Click here to Skip to main content
15,896,727 members
Articles / Desktop Programming / Win32

Outlook add-in integrating Skype

Rate me:
Please Sign up or sign in to vote.
4.93/5 (32 votes)
28 May 2015CPOL20 min read 69K   2.4K   54  
Outlook add-in integration for Skype IM: Skype events, Outlook Skype ribbon, and more.
#include "Sdk.h"
#include "SkypeComm.h"
#include "SkypeDefs.h"
#include "DbgTrace.h"
#include "Thread.h"
#include "Addin.h"
#include "MemMgr.h"
#include "StringMgr.h"
#include "StrUtils.h"
#include "_IDTExtensibility2.h"
#include "Addin.h"

static
const wchar_t *
kSkypeWatchWndClassName = L"OLNDesk.OutlookAddin:Windows:SkypeWatch";

//  re-post since we need to be PostMessage, not SendMessage
static UINT OLNdesk_WM_COPYDATA                 = (UINT)-1;
static UINT OLNdesk_msg_SkypeControlAPIAttach   = (UINT)-1;

const wchar_t* SKYPE_API_STATUS_string(SKYPE_API_STATUS status) {
    switch(status) {
        case SKYPECONTROLAPI_ATTACH_UNDEFINED:    
            return L"Unknown";
        case SKYPECONTROLAPI_ATTACH_SUCCESS:    
            return L"Success";
        case SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION:
            return L"Authorization Pending";
        case SKYPECONTROLAPI_ATTACH_REFUSED:
            return L"Refused";
        case SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE:
            return L"Not available";
        case SKYPECONTROLAPI_ATTACH_API_AVAILABLE:
            return L"Available";
        default:
            return L"Unknown";
    }
}

static
BOOL
GetAddinFromStream(IStream   *pstmAddin, 
                   IDispatch **ppDispAddin
) {
    HRESULT                 hr          = S_FALSE;
    LARGE_INTEGER           li = { 0 };
    IUnknown                *punkAddin  = NULL;

    BOOL                    Result      = FALSE;

    __try {
        if(ppDispAddin == NULL) {
            __leave;
        }
        *ppDispAddin = NULL;

        if(pstmAddin == NULL) {
            __leave;
        }
        hr = IStream_Seek(pstmAddin, 
                          li, 
                          STREAM_SEEK_SET, 
                          NULL
                         );
        if(FAILED(hr)) {
            __leave;
        }
        hr = CoUnmarshalInterface(pstmAddin, 
                                  &IID_IDispatch, 
                                  (void **)ppDispAddin
                                 );
        if(FAILED(hr)) {
            CoReleaseMarshalData(pstmAddin);
            __leave;
        }

        Result = *ppDispAddin != NULL;
    }
    __finally {
        if(punkAddin != NULL) {
            IUnknown_Release(punkAddin);
        }
    }

    return Result;
}

LRESULT CALLBACK 
SkypeWatchWndProc(HWND hwnd, 
                  UINT msg, 
                  WPARAM wp,
                  LPARAM lp
) {
    static UINT             msg_SkypeControlAPIDiscover = 0;
    static UINT             msg_SkypeControlAPIAttach   = 0;
    static UINT_PTR         ConnectTimerID              = 0;
    static SKYPE_API_STATUS APIStatus                   = SKYPECONTROLAPI_ATTACH_UNDEFINED;
    XAddin                  *pAddin                     = NULL;

    LRESULT                 Result      = 0;
    BOOL                    Handled     = FALSE;

    //  we can access variables from pAddin, 
    //  but we don't want to make calls into addin from a different thread
    //  if COM calls 

    if(OLNdesk_WM_COPYDATA == (UINT)-1) {
        OLNdesk_WM_COPYDATA = RegisterWindowMessageW(L"OLNdesk_WM_COPYDATA");
    }
    if(OLNdesk_msg_SkypeControlAPIAttach == (UINT)-1) {
        OLNdesk_msg_SkypeControlAPIAttach = RegisterWindowMessageW(L"OLNdesk_msg_SkypeControlAPIAttach");        
    }

    if(msg == WM_NCCREATE) {
        SetPropW(hwnd, 
                 L"Addin", 
                 (HANDLE)
                 (
                    (XAddin *)
                    (
                        ((Thread *)(((((LPCREATESTRUCTW)lp)->lpCreateParams))))->ThreadParam
                    )
                 )
                );
    }
    else if (msg == WM_CLOSE) {
        PostQuitMessage(0);
        return 0;
    }

    //  this is the STA 0 pAddin object; can't invoke COM direct or indirect calls on this thread
    pAddin = (XAddin *)GetPropW(hwnd, L"Addin");


    if(msg == WM_CREATE) {
        //  start connection process with Skype
        ConnectTimerID = SetTimer(hwnd, 1, 1000, NULL);

        msg_SkypeControlAPIDiscover = RegisterWindowMessageW(L"SkypeControlAPIDiscover");
        msg_SkypeControlAPIAttach   = RegisterWindowMessageW(L"SkypeControlAPIAttach");

        DTrace3(TRACE_LEVEL_DEBUG, 
            L"SkypeWatch/WM_CREATE ConnectTimerID=%ld msg_SkypeControlAPIDiscover=%ld msg_SkypeControlAPIAttach=%ld", 
            (UINT)ConnectTimerID, 
            msg_SkypeControlAPIDiscover, 
            msg_SkypeControlAPIAttach
            );
    }
    else if(msg == WM_TIMER) {
        if(wp == ConnectTimerID) {
            LRESULT discoverResult;

            if(APIStatus == SKYPECONTROLAPI_ATTACH_UNDEFINED 
            || (pAddin->SkypeInfo.ApiStatus == SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE)) {
                if(!pAddin->IsSkypeRunning(pAddin)) {
                    APIStatus = SKYPECONTROLAPI_ATTACH_UNDEFINED;
                    PostMessage(hwnd, OLNdesk_msg_SkypeControlAPIAttach, 0, (WPARAM)SKYPECONTROLAPI_ATTACH_UNDEFINED);
                }
            }

            if(APIStatus == SKYPECONTROLAPI_ATTACH_UNDEFINED 
            || (pAddin->SkypeInfo.ApiStatus == SKYPECONTROLAPI_ATTACH_API_AVAILABLE && pAddin->SkypeInfo.ApiHWND == NULL)) {
#pragma warning(disable:4306)
                discoverResult = SendMessageTimeoutW(HWND_BROADCAST, 
                                                     msg_SkypeControlAPIDiscover, 
                                                     (WPARAM)hwnd,
                                                     0, 
                                                     SMTO_ABORTIFHUNG, 
                                                     1000, 
                                                     NULL
                                                    );
#pragma warning(default:4306)

                if(APIStatus != SKYPECONTROLAPI_ATTACH_UNDEFINED) {
                    DTrace4(TRACE_LEVEL_DEBUG, 
                        L"SkypeWatch/WM_TIMER timerID=%ld discoverResult=%ld APIStatus=%ld (%s)", 
                        (UINT)ConnectTimerID, 
                        discoverResult, 
                        APIStatus, SKYPE_API_STATUS_string((SKYPE_API_STATUS)APIStatus)
                        );
                }
            }
            else if(pAddin->SkypeInfo.ApiHWND != NULL) {
                if(!IsWindow(pAddin->SkypeInfo.ApiHWND)) {
                    DTrace3(TRACE_LEVEL_DEBUG, 
                        L"SkypeWatch/WM_TIMER APIStatus=%ld (%s) ApiHWND=0x%p is not a valid HWND anymore. Skype has quit.", 
                        APIStatus, SKYPE_API_STATUS_string((SKYPE_API_STATUS)APIStatus), 
                        pAddin->SkypeInfo.ApiHWND
                        );

                #if 0
                    pAddin->SetSkypeApiStatus(pAddin, SKYPECONTROLAPI_ATTACH_UNDEFINED);
                #else
                    APIStatus = SKYPECONTROLAPI_ATTACH_UNDEFINED;
                    PostMessage(hwnd, OLNdesk_msg_SkypeControlAPIAttach, 0, (WPARAM)SKYPECONTROLAPI_ATTACH_UNDEFINED);
                #endif
                }
            }
        }
    }
    else if(msg == msg_SkypeControlAPIAttach) {
        //  skype answer to discover
        APIStatus = (SKYPE_API_STATUS)lp;

        DTrace3(TRACE_LEVEL_DEBUG, 
                    L"SkypeWatch/msg_SkypeControlAPIAttach=%ld APIStatus=%ld (%ls)", 
                    msg_SkypeControlAPIAttach, 
                    APIStatus, 
                    SKYPE_API_STATUS_string(APIStatus)
                    );

        switch(APIStatus) {

            case SKYPECONTROLAPI_ATTACH_SUCCESS:
                DTrace0(TRACE_LEVEL_DEBUG, 
                    L"SKYPECONTROLAPI_ATTACH_SUCCESS. Attach successful, can start sending commands."
                );
                //  this is not a MTA call, just set an internal property of pAddin
                pAddin->SetSkypeApiHWND(pAddin, (HWND)wp);
                break;

            case SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION:
                DTrace0(TRACE_LEVEL_DEBUG, 
                    L"SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION. Waiting for authorization."
                );
                break;

            case SKYPECONTROLAPI_ATTACH_REFUSED:
                DTrace0(TRACE_LEVEL_DEBUG, 
                    L"SKYPECONTROLAPI_ATTACH_REFUSED. User denied access to Skype."
                );
                break;

            case SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE:
                DTrace0(TRACE_LEVEL_DEBUG, 
                    L"SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE. API is not available."
                );
                break;

            case SKYPECONTROLAPI_ATTACH_API_AVAILABLE:
                DTrace0(TRACE_LEVEL_DEBUG, 
                    L"SKYPECONTROLAPI_ATTACH_API_AVAILABLE. API has becoming available."
                );
                break;

            default:
                break;
        }

        //  MTA call; invokes ribbon invalidate
#if 0
        pAddin->SetSkypeApiStatus(pAddin, APIStatus);
#else
        PostMessage(hwnd, OLNdesk_msg_SkypeControlAPIAttach, wp, lp);
#endif

        Result  = (LRESULT)TRUE;
        Handled = TRUE;
    }
    else if(msg == OLNdesk_msg_SkypeControlAPIAttach) {
        //  invoke interthread call on pAddin
        IDispatch   *pDispAddin = NULL;
        DISPPARAMS  dp          = {0};
        VARIANT     var;
        HRESULT     hr          = S_FALSE;
        int         ps__        = 0;

        __try {
            VariantInit(&var);

            if(!GetAddinFromStream(pAddin->pstmAddin, &pDispAddin)) {
                __leave;
            }

            V_VT(&var) = VT_I4;
            V_I4(&var) = (long)APIStatus;

            dp.cArgs    = 1;
            dp.rgvarg   = &var;

            hr = IDispatch_Invoke(pDispAddin, 
                                    DISPID_ADDIN_SETAPISTATUS, 
                                    &IID_NULL, 
                                    0, 
                                    DISPATCH_METHOD, 
                                    &dp, 
                                    NULL, 
                                    NULL, 
                                    NULL
                                    );
            if(FAILED(hr)) {
                __leave;
            }
            ps__ = 0;
        }
        __finally {
            if(pDispAddin != NULL) {
                IDispatch_Release(pDispAddin);
            }

            VariantClear(&var);
        }

        return 0;
    }

    else if(msg == WM_COPYDATA) {
        HWND            hwndSender  = (HWND)wp;
        PCOPYDATASTRUCT pcds        = (PCOPYDATASTRUCT)lp;
        DWORD_PTR       Context     = 0;

        if(pcds != NULL) {
            //  reput the message, but as PostMessage
            PCOPYDATASTRUCT_CTX pPostCDS = NULL;

            //  retrieve call context
            Context = (DWORD_PTR)GetPropW(hwnd, L"SkypeCall.Context");
            SetPropW(pAddin->SkypeWatchThread->ThreadHWND, 
                     L"SkypeCall.Context", 
                     (HANDLE)(0)
                    );

            Allocator_AllocRef(sizeof(COPYDATASTRUCT_CTX), 
                               &pPostCDS
                              );
            if(pPostCDS != NULL) {
                pPostCDS->dwContext = Context;
                pPostCDS->dwData    = pcds->dwData;
                pPostCDS->cbData    = pcds->cbData;

                if(pcds->cbData != 0) {
                    Allocator_AllocRef(pcds->cbData, 
                                       &pPostCDS->lpData
                                      );
                    if(pPostCDS->lpData == NULL) {
                        Allocator_FreeRef(&pPostCDS);
                    }
                    else {
                        memcpy(pPostCDS->lpData, pcds->lpData, pcds->cbData);
                        PostMessage(hwnd, OLNdesk_WM_COPYDATA, (WPARAM)hwndSender, (LPARAM)pPostCDS);
                    }
                }
            }
        }

        Result  = (LRESULT)TRUE;
        Handled = TRUE;
    }
    else if(msg == OLNdesk_WM_COPYDATA) {
        HWND                hwndSender  = (HWND)wp;
        PCOPYDATASTRUCT_CTX pcds        = (PCOPYDATASTRUCT_CTX)lp;

        if(pcds == NULL) {
            DTrace1(TRACE_LEVEL_DEBUG, 
                    L"SkypeWatch/WM_COPYDATA hwndSender=0x%p pcds is NULL", 
                    hwndSender
                   );
        }
        else {
            DWORD_PTR Context = pcds->dwContext;
#if 0
            {
                LPWSTR lpDataW = NULL;
                StrUtf8ToUnicode(&lpDataW, 
                                 (const BYTE*)pcds->lpData, 
                                 -1
                                 );

                DTrace3(TRACE_LEVEL_DEBUG, 
                        L"SkypeWatch/WM_COPYDATA hwndSender=0x%p pcds->dwContext=%ld pcds->lpData=%s", 
                        hwndSender, 
                        pcds->dwContext, 
                        lpDataW
                       );
                Allocator_FreeRef(&lpDataW);
            }
#endif

            //  process the message from Skype
            if(pAddin->SkypeInfo.ApiHWND != NULL
            && hwndSender == pAddin->SkypeInfo.ApiHWND) {
                //  MTA call
                    //  cannot call pAddin->ProcessSkypeMessage(pAddin, pcds);
                //  invoke interthread call on pAddin
                IDispatch   *pDispAddin = NULL;
                DISPPARAMS  dp          = {0};
                VARIANT     var[2];
                SAFEARRAYBOUND rgsa[1];
                void        *pData      = NULL;
                HRESULT     hr          = S_FALSE;
                int         ps__        = 0;

                __try {
                    VariantInit(&var[0]);
                    VariantInit(&var[1]);

                    if(!GetAddinFromStream(pAddin->pstmAddin, &pDispAddin)) {
                        __leave;
                    }

                    V_VT(&var[0]) = VT_I4;
                    V_I4(&var[0]) = (LONG)Context;

                    V_VT(&var[1]) = VT_UI1 | VT_ARRAY;
                    rgsa[0].cElements   = pcds->cbData;
                    rgsa[0].lLbound     = 0;

                    var[1].parray = SafeArrayCreate(VT_UI1, 1, rgsa);
                    if(var[1].parray == NULL) {
                        __leave;
                    }
                    hr = SafeArrayAccessData(var[1].parray, &pData);
                    memcpy(pData, pcds->lpData, pcds->cbData);
                    hr = SafeArrayUnaccessData(var[1].parray);

                    dp.cArgs    = 2;
                    dp.rgvarg   = &var[0];

                    hr = IDispatch_Invoke(pDispAddin, 
                                            DISPID_ADDIN_PROCESSSKYPEMESSAGE, 
                                            &IID_NULL, 
                                            0, 
                                            DISPATCH_METHOD, 
                                            &dp, 
                                            NULL, 
                                            NULL, 
                                            NULL
                                            );
                    if(FAILED(hr)) {
                        __leave;
                    }
                    ps__ = 0;
                }
                __finally {
                    if(pDispAddin != NULL) {
                        IDispatch_Release(pDispAddin);
                    }

                    VariantClear(&var[0]);
                    VariantClear(&var[1]);
                }
            }

            Allocator_FreeRef(&pcds->lpData);
            Allocator_FreeRef(&pcds);
        }

        Result  = (LRESULT)TRUE;
        Handled = TRUE;
    }
    /*
    if(piAddin != NULL) {
        IDTExtensibility2_Release(piAddin);
    }
    */
    if(Handled) {
        return Result;
    }

    return DefWindowProcW(hwnd, msg, wp, lp);
}

UINT
__stdcall
SkypeWatch(Thread *pThread) {
    BOOL        fShouldEnd      = FALSE;
    WNDCLASSEXW wc              = {0};
    HWND        hwndSkypeWatch  = NULL;
    MSG         msg             = {0};
    UINT        Result          = 0;

    __try {
        if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) {
            Result = (UINT)-1;
            __leave;
        }

        if(pThread == NULL) {
            Result = (UINT)-1;
            __leave;
        }

        //  create listener/IPC window
        wc.cbSize = sizeof(wc);
        wc.cbClsExtra       = 0;
        wc.cbWndExtra       = 0;
        wc.hbrBackground    = NULL;
        wc.hCursor          = NULL;
        wc.hIcon            = NULL;
        wc.hIconSm          = NULL;
        wc.hInstance        = NULL;
        wc.lpfnWndProc      = SkypeWatchWndProc;
        wc.lpszClassName    = kSkypeWatchWndClassName;
        wc.lpszMenuName     = NULL;
        wc.style            = 0;

        if(RegisterClassExW(&wc) == 0) {
            Result = (UINT)-2;
            __leave;
        }

        hwndSkypeWatch = CreateWindowExW(0, 
                                         kSkypeWatchWndClassName, 
                                         L"OLNDesk.OutlookAddin.SkypeWatchWindow", 
                                         WS_POPUP, 
                                         -1, 
                                         -1, 
                                         0, 
                                         0, 
                                         NULL, 
                                         NULL, 
                                         NULL, 
                                         (LPVOID)pThread
                                        );
        if(hwndSkypeWatch == NULL) {
            Result = (UINT)-3;
            __leave;
        }

        pThread->ThreadHWND = hwndSkypeWatch;

        ShowWindow(hwndSkypeWatch, SW_HIDE);
        UpdateWindow(hwndSkypeWatch);

        while(!fShouldEnd) {

            //  consume messages
            while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
                if(msg.message == WM_QUIT) {
                    DTrace0(TRACE_LEVEL_DEBUG, 
                        L"SkypeWatch/WM_QUIT received"
                       );
                    fShouldEnd = TRUE;
                }

                TranslateMessage(&msg);
                DispatchMessageW(&msg);
            }

            if(fShouldEnd) {
                break;
            }

            //  check for end event
            if(MsgWaitForMultipleObjects(1, 
                                         &pThread->SignalEndEvent, 
                                         FALSE, 
                                         INFINITE, 
                                         QS_ALLINPUT
                                        ) == WAIT_OBJECT_0) {
                DestroyWindow(hwndSkypeWatch);
#if 0
                PostThreadMessageW(GetCurrentThreadId(), 
                                   WM_QUIT, 
                                   0, 
                                   0
                                  );
#else
                PostQuitMessage(0);
#endif
            }
        }

        Result = (UINT)msg.wParam;
    }
    __finally {
        CoUninitialize();
    }

    return Result;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader BitDefender
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions