/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <memory>
#include <sal/config.h>

#include <cassert>

#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>

#include <rtl/strbuf.hxx>
#include <sal/log.hxx>

#include <rtl/process.h>
#include <osl/security.h>
#include <osl/diagnose.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <unx/sm.hxx>
#include <unx/saldisp.hxx>
#include <unx/salinst.h>

#include <vcl/svapp.hxx>
#include <vcl/window.hxx>

#include <salframe.hxx>
#include <salsession.hxx>

namespace {

class IceSalSession : public SalSession
{
public:
    IceSalSession() {}

private:
    virtual ~IceSalSession() override {}

    virtual void queryInteraction() override;
    virtual void interactionDone() override;
    virtual void saveDone() override;
    virtual bool cancelShutdown() override;
};

}

std::unique_ptr<SalSession> X11SalInstance::CreateSalSession()
{
    SAL_INFO("vcl.sm", "X11SalInstance::CreateSalSession");

    std::unique_ptr<SalSession> p(new IceSalSession);
    SessionManagerClient::open(p.get());
    return p;
}

void IceSalSession::queryInteraction()
{
    SAL_INFO("vcl.sm", "IceSalSession::queryInteraction");

    if( ! SessionManagerClient::queryInteraction() )
    {
        SAL_INFO("vcl.sm.debug", "  call SalSessionInteractionEvent");
        SalSessionInteractionEvent aEvent( false );
        CallCallback( &aEvent );
    }
}

void IceSalSession::interactionDone()
{
    SAL_INFO("vcl.sm", "IceSalSession::interactionDone");

    SessionManagerClient::interactionDone( false );
}

void IceSalSession::saveDone()
{
    SAL_INFO("vcl.sm", "IceSalSession::saveDone");

    SessionManagerClient::saveDone();
}

bool IceSalSession::cancelShutdown()
{
    SAL_INFO("vcl.sm", "IceSalSession::cancelShutdown");

    SessionManagerClient::interactionDone( true );
    return false;
}

extern "C" {

static void ICEWatchProc(
    IceConn ice_conn, IcePointer client_data, Bool opening,
    IcePointer * watch_data);

static void ICEConnectionWorker(void * data);

}

class ICEConnectionObserver
{
    friend void ICEWatchProc(IceConn, IcePointer, Bool, IcePointer *);

    friend void ICEConnectionWorker(void *);

    struct pollfd* m_pFilehandles;
    int m_nConnections;
    IceConn* m_pConnections;
    int m_nWakeupFiles[2];
    oslThread m_ICEThread;
    IceIOErrorHandler m_origIOErrorHandler;
    IceErrorHandler m_origErrorHandler;

    void wakeup();

public:
    osl::Mutex m_ICEMutex;

    ICEConnectionObserver()
        : m_pFilehandles(nullptr)
        , m_nConnections(0)
        , m_pConnections(nullptr)
        , m_ICEThread(nullptr)
        , m_origIOErrorHandler(nullptr)
        , m_origErrorHandler(nullptr)
    {
        SAL_INFO("vcl.sm", "ICEConnectionObserver::ICEConnectionObserver");

        m_nWakeupFiles[0] = m_nWakeupFiles[1] = 0;
    }

    void activate();
    void deactivate();
    void terminate(oslThread iceThread);
};

SalSession * SessionManagerClient::m_pSession = nullptr;
std::unique_ptr< ICEConnectionObserver >
SessionManagerClient::m_xICEConnectionObserver;
SmcConn SessionManagerClient::m_pSmcConnection = nullptr;
OString SessionManagerClient::m_aClientID = ""_ostr;
OString SessionManagerClient::m_aTimeID = ""_ostr;
OString SessionManagerClient::m_aClientTimeID = ""_ostr;
bool SessionManagerClient::m_bDocSaveDone = false; // HACK

extern "C" {

static void IgnoreIceErrors(
    SAL_UNUSED_PARAMETER IceConn, SAL_UNUSED_PARAMETER Bool,
    SAL_UNUSED_PARAMETER int, SAL_UNUSED_PARAMETER unsigned long,
    SAL_UNUSED_PARAMETER int, SAL_UNUSED_PARAMETER int,
    SAL_UNUSED_PARAMETER IcePointer)
{}

static void IgnoreIceIOErrors(SAL_UNUSED_PARAMETER IceConn) {}

}

static SmProp*  pSmProps = nullptr;
static SmProp** ppSmProps = nullptr;
static char  ** ppSmDel  = nullptr;

static int      nSmProps = 0;
static int      nSmDel   = 0;
static unsigned char   *pSmRestartHint = nullptr;


enum { eCloneCommand, eProgram, eRestartCommand, eUserId, eRestartStyleHint };
enum { eDiscardCommand };


static void BuildSmPropertyList()
{
    SAL_INFO("vcl.sm", "BuildSmPropertyList");

    if( ! pSmProps )
    {
        nSmProps  = 5;
        nSmDel    = 1;
        pSmProps  = new SmProp[ nSmProps ];
        ppSmProps = new SmProp*[ nSmProps ];
        ppSmDel   = new char*[ nSmDel ];
    }

    OString aExec(OUStringToOString(SessionManagerClient::getExecName(), osl_getThreadTextEncoding()));

    pSmProps[ eCloneCommand ].name      = const_cast<char*>(SmCloneCommand);
    pSmProps[ eCloneCommand ].type      = const_cast<char*>(SmLISTofARRAY8);
    pSmProps[ eCloneCommand ].num_vals  = 1;
    pSmProps[ eCloneCommand ].vals      = new SmPropValue;
    pSmProps[ eCloneCommand ].vals->length  = aExec.getLength()+1;
    pSmProps[ eCloneCommand ].vals->value   = strdup( aExec.getStr() );

    pSmProps[ eProgram ].name      = const_cast<char*>(SmProgram);
    pSmProps[ eProgram ].type      = const_cast<char*>(SmARRAY8);
    pSmProps[ eProgram ].num_vals  = 1;
    pSmProps[ eProgram ].vals      = new SmPropValue;
    pSmProps[ eProgram ].vals->length  = aExec.getLength()+1;
    pSmProps[ eProgram ].vals->value   = strdup( aExec.getStr() );

    pSmProps[ eRestartCommand ].name      = const_cast<char*>(SmRestartCommand);
    pSmProps[ eRestartCommand ].type      = const_cast<char*>(SmLISTofARRAY8);
    pSmProps[ eRestartCommand ].num_vals  = 3;
    pSmProps[ eRestartCommand ].vals      = new SmPropValue[3];
    pSmProps[ eRestartCommand ].vals[0].length    = aExec.getLength()+1;
    pSmProps[ eRestartCommand ].vals[0].value = strdup( aExec.getStr() );
    OString aRestartOption = "--session=" + SessionManagerClient::getSessionID();
    pSmProps[ eRestartCommand ].vals[1].length    = aRestartOption.getLength()+1;
    pSmProps[ eRestartCommand ].vals[1].value = strdup(aRestartOption.getStr());
    OString aRestartOptionNoLogo("--nologo"_ostr);
    pSmProps[ eRestartCommand ].vals[2].length    = aRestartOptionNoLogo.getLength()+1;
    pSmProps[ eRestartCommand ].vals[2].value = strdup(aRestartOptionNoLogo.getStr());

    OUString aUserName;
    OString aUser;
    oslSecurity aSec = osl_getCurrentSecurity();
    if( aSec )
    {
        osl_getUserName( aSec, &aUserName.pData );
        aUser = OUStringToOString( aUserName, osl_getThreadTextEncoding() );
        osl_freeSecurityHandle( aSec );
    }

    pSmProps[ eUserId ].name      = const_cast<char*>(SmUserID);
    pSmProps[ eUserId ].type      = const_cast<char*>(SmARRAY8);
    pSmProps[ eUserId ].num_vals  = 1;
    pSmProps[ eUserId ].vals      = new SmPropValue;
    pSmProps[ eUserId ].vals->value   = strdup( aUser.getStr() );
    pSmProps[ eUserId ].vals->length  = rtl_str_getLength( static_cast<char *>(pSmProps[ 3 ].vals->value) )+1;

    pSmProps[ eRestartStyleHint ].name      = const_cast<char*>(SmRestartStyleHint);
    pSmProps[ eRestartStyleHint ].type      = const_cast<char*>(SmCARD8);
    pSmProps[ eRestartStyleHint ].num_vals  = 1;
    pSmProps[ eRestartStyleHint ].vals      = new SmPropValue;
    pSmProps[ eRestartStyleHint ].vals->value   = malloc(1);
    pSmRestartHint = static_cast<unsigned char *>(pSmProps[ 4 ].vals->value);
    *pSmRestartHint = SmRestartIfRunning;
    pSmProps[ eRestartStyleHint ].vals->length  = 1;

    for( int i = 0; i < nSmProps; i++ )
        ppSmProps[ i ] = &pSmProps[i];

    ppSmDel[eDiscardCommand] = const_cast<char*>(SmDiscardCommand);
}

bool SessionManagerClient::checkDocumentsSaved()
{
    SAL_INFO("vcl.sm", "SessionManagerClient::checkDocumentsSaved");

    SAL_INFO("vcl.sm.debug", "  m_bcheckDocumentsSaved = " << (m_bDocSaveDone ? "true" : "false" ));
    return m_bDocSaveDone;
}

IMPL_STATIC_LINK( SessionManagerClient, SaveYourselfHdl, void*, pStateVal, void )
{
    SAL_INFO("vcl.sm", "SessionManagerClient, SaveYourselfHdl");

    // Decode argument smuggled in as void*:
    sal_uIntPtr nStateVal = reinterpret_cast< sal_uIntPtr >(pStateVal);
    bool shutdown = nStateVal != 0;

    static bool bFirstShutdown=true;

    SAL_INFO("vcl.sm.debug", "  shutdown  = " << (shutdown ? "true" : "false" ) <<
                             ", bFirstShutdown = " << (bFirstShutdown ? "true" : "false" ));
    if (shutdown && bFirstShutdown) //first shutdown request
    {
        bFirstShutdown = false;
        /*
          If we have no actual frames open, e.g. we launched a quickstarter,
          and then shutdown all our frames leaving just a quickstarter running,
          then we don't want to launch an empty toplevel frame on the next
          start. (The job of scheduling the restart of the quick-starter is a
          task of the quick-starter)
        */
        *pSmRestartHint = SmRestartNever;
        for (auto pSalFrame : vcl_sal::getSalDisplay(GetGenericUnixSalData())->getFrames() )
        {
            vcl::Window *pWindow = pSalFrame->GetWindow();
            if (pWindow && pWindow->IsVisible())
            {
                *pSmRestartHint = SmRestartIfRunning;
                SAL_INFO("vcl.sm.debug", "  pSmRestartHint = SmRestartIfRunning");
                break;
            }
        }
    }

    if( m_pSession )
    {
        SalSessionSaveRequestEvent aEvent( shutdown );
        m_pSession->CallCallback( &aEvent );
    }
    else
        saveDone();
}

IMPL_STATIC_LINK_NOARG( SessionManagerClient, InteractionHdl, void*, void )
{
    SAL_INFO("vcl.sm", "SessionManagerClient, InteractionHdl");

    if( m_pSession )
    {
        SalSessionInteractionEvent aEvent( true );
        m_pSession->CallCallback( &aEvent );
    }
}

IMPL_STATIC_LINK_NOARG( SessionManagerClient, ShutDownCancelHdl, void*, void )
{
    SAL_INFO("vcl.sm", "SessionManagerClient, ShutDownCancelHdl");

    if( m_pSession )
    {
        SalSessionShutdownCancelEvent aEvent;
        m_pSession->CallCallback( &aEvent );
    }
}

void SessionManagerClient::SaveYourselfProc(
    SmcConn,
    SmPointer,
    int save_type,
    Bool shutdown,
    int interact_style,
    Bool
    )
{
    SAL_INFO("vcl.sm", "SessionManagerClient::SaveYourselfProc");

    TimeValue now;
    osl_getSystemTime(&now);

    SAL_INFO("vcl.sm", "  save_type = "   <<    ((save_type == SmSaveLocal ) ? "local"  :
                                                 (save_type == SmSaveGlobal) ? "global" : "both") <<
                       ", shutdown = " <<        (shutdown ? "true" : "false" ) <<
                       ", interact_style = " << ((interact_style == SmInteractStyleNone)   ? "SmInteractStyleNone"   :
                                                 (interact_style == SmInteractStyleErrors) ? "SmInteractStyleErrors" :
                                                                                             "SmInteractStyleAny"));
    char num[100];
    snprintf(num, sizeof(num), "_%" SAL_PRIuUINT32 "_%" SAL_PRIuUINT32, now.Seconds, (now.Nanosec / 1001));
    m_aTimeID = OString(num);

    BuildSmPropertyList();

    SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eProgram ] );
    SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eUserId ] );


    m_bDocSaveDone = false;
    /* #i49875# some session managers send a "die" message if the
     * saveDone does not come early enough for their convenience
     * this can occasionally happen on startup, especially the first
     * startup. So shortcut the "not shutting down" case since the
     * upper layers are currently not interested in that event anyway.
     */
    if( ! shutdown )
    {
        SessionManagerClient::saveDone();
        return;
    }
    // Smuggle argument in as void*:
    sal_uIntPtr nStateVal = shutdown;
    Application::PostUserEvent( LINK( nullptr, SessionManagerClient, SaveYourselfHdl ), reinterpret_cast< void * >(nStateVal) );
}

IMPL_STATIC_LINK_NOARG( SessionManagerClient, ShutDownHdl, void*, void )
{
    SAL_INFO("vcl.sm", "SessionManagerClient, ShutDownHdl");

    if( m_pSession )
    {
        SalSessionQuitEvent aEvent;
        m_pSession->CallCallback( &aEvent );
    }

    SalFrame *pAnyFrame = vcl_sal::getSalDisplay(GetGenericUnixSalData())->anyFrame();
    SAL_INFO("vcl.sm.debug", "  rFrames.empty() = " << (pAnyFrame ? "true" : "false"));
    if( pAnyFrame )
        pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr );
}

void SessionManagerClient::DieProc(
    SmcConn connection,
    SmPointer
    )
{
    SAL_INFO("vcl.sm", "SessionManagerClient::DieProc");

    if( connection == m_pSmcConnection )
    {
        SAL_INFO("vcl.sm.debug", "  connection == m_pSmcConnection" );
        Application::PostUserEvent( LINK( nullptr, SessionManagerClient, ShutDownHdl ) );
    }
}

void SessionManagerClient::SaveCompleteProc(
    SmcConn,
    SmPointer
    )
{
    SAL_INFO("vcl.sm", "SessionManagerClient::SaveCompleteProc");
}

void SessionManagerClient::ShutdownCanceledProc(
    SmcConn connection,
    SmPointer )
{
    SAL_INFO("vcl.sm", "SessionManagerClient::ShutdownCanceledProc" );

    SAL_INFO("vcl.sm.debug", "  connection == m_pSmcConnection = " <<  (( connection == m_pSmcConnection ) ? "true" : "false"));
    if( connection == m_pSmcConnection )
        Application::PostUserEvent( LINK( nullptr, SessionManagerClient, ShutDownCancelHdl ) );
}

void SessionManagerClient::InteractProc(
                                        SmcConn connection,
                                        SmPointer )
{
    SAL_INFO("vcl.sm", "SessionManagerClient::InteractProc" );

    SAL_INFO("vcl.sm.debug", "  connection == m_pSmcConnection = " <<  (( connection == m_pSmcConnection ) ? "true" : "false"));
    if( connection == m_pSmcConnection )
        Application::PostUserEvent( LINK( nullptr, SessionManagerClient, InteractionHdl ) );
}

void SessionManagerClient::saveDone()
{
    SAL_INFO("vcl.sm", "SessionManagerClient::saveDone");

    if( !m_pSmcConnection )
        return;

    assert(m_xICEConnectionObserver);
    osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
    //SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eCloneCommand ] );
    // this message-handling is now equal to kate and plasma desktop
    SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eRestartCommand ] );
    SmcDeleteProperties( m_pSmcConnection, 1, &ppSmDel[ eDiscardCommand ] );
    SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eRestartStyleHint ] );

    SmcSaveYourselfDone( m_pSmcConnection, True );
    SAL_INFO("vcl.sm.debug", "  sent SmRestartHint = " << (*pSmRestartHint) );
    m_bDocSaveDone = true;
}

void SessionManagerClient::open(SalSession * pSession)
{
    SAL_INFO("vcl.sm", "SessionManagerClient::open");

    assert(!m_pSession && !m_xICEConnectionObserver && !m_pSmcConnection);
        // must only be called once
    m_pSession = pSession;
    // This is the way Xt does it, so we can too:
    if( getenv( "SESSION_MANAGER" ) )
    {
        SAL_INFO("vcl.sm.debug", "  getenv( SESSION_MANAGER ) = true");
        m_xICEConnectionObserver.reset(new ICEConnectionObserver);
        m_xICEConnectionObserver->activate();

        {
            osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);

            static SmcCallbacks aCallbacks; // does this need to be static?
            aCallbacks.save_yourself.callback           = SaveYourselfProc;
            aCallbacks.save_yourself.client_data        = nullptr;
            aCallbacks.die.callback                     = DieProc;
            aCallbacks.die.client_data                  = nullptr;
            aCallbacks.save_complete.callback           = SaveCompleteProc;
            aCallbacks.save_complete.client_data        = nullptr;
            aCallbacks.shutdown_cancelled.callback      = ShutdownCanceledProc;
            aCallbacks.shutdown_cancelled.client_data   = nullptr;
            OString aPrevId(getPreviousSessionID());
            char* pClientID = nullptr;
            char aErrBuf[1024];
            m_pSmcConnection = SmcOpenConnection( nullptr,
                                                  nullptr,
                                                  SmProtoMajor,
                                                  SmProtoMinor,
                                                  SmcSaveYourselfProcMask         |
                                                  SmcDieProcMask                  |
                                                  SmcSaveCompleteProcMask         |
                                                  SmcShutdownCancelledProcMask    ,
                                                  &aCallbacks,
                                                  aPrevId.isEmpty() ? nullptr : const_cast<char*>(aPrevId.getStr()),
                                                  &pClientID,
                                                  sizeof( aErrBuf ),
                                                  aErrBuf );
            if( !m_pSmcConnection )
                SAL_INFO("vcl.sm.debug", "  SmcOpenConnection failed: " << aErrBuf);
            else
                SAL_INFO("vcl.sm.debug", "  SmcOpenConnection succeeded, client ID is " << pClientID );

            if (pClientID)
                m_aClientID = OString(pClientID);
            free( pClientID );
            pClientID = nullptr;
        }

        SalDisplay* pDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
        if( pDisp->GetDrawable(pDisp->GetDefaultXScreen()) && !m_aClientID.isEmpty() )
        {
            SAL_INFO("vcl.sm.debug", "  SmcOpenConnection open: pDisp->GetDrawable = true");
            XChangeProperty( pDisp->GetDisplay(),
                             pDisp->GetDrawable( pDisp->GetDefaultXScreen() ),
                             XInternAtom( pDisp->GetDisplay(), "SM_CLIENT_ID", False ),
                             XA_STRING,
                             8,
                             PropModeReplace,
                             reinterpret_cast<unsigned char const *>(m_aClientID.getStr()),
                             m_aClientID.getLength()
                             );
        }
    }
    else
    {
        SAL_INFO("vcl.sm.debug", "  getenv( SESSION_MANAGER ) = false");
    }
}

const OString& SessionManagerClient::getSessionID()
{
    SAL_INFO("vcl.sm", "SessionManagerClient::getSessionID");

    m_aClientTimeID = m_aClientID + m_aTimeID;

    SAL_INFO("vcl.sm", "  SessionID = " << m_aClientTimeID);

    return m_aClientTimeID;
}

void SessionManagerClient::close()
{
    SAL_INFO("vcl.sm", "SessionManagerClient::close");

    if( !m_pSmcConnection )
        return;

    SAL_INFO("vcl.sm.debug", "  attempting SmcCloseConnection");
    assert(m_xICEConnectionObserver);
    {
        osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
        SmcCloseConnection( m_pSmcConnection, 0, nullptr );
        SAL_INFO("vcl.sm", "  SmcCloseConnection closed");
    }
    m_xICEConnectionObserver->deactivate();
    m_xICEConnectionObserver.reset();
    m_pSmcConnection = nullptr;
}

bool SessionManagerClient::queryInteraction()
{
    SAL_INFO("vcl.sm", "SessionManagerClient::queryInteraction");

    bool bRet = false;
    if( m_pSmcConnection )
    {
        assert(m_xICEConnectionObserver);
        osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
        SAL_INFO("vcl.sm.debug", "  SmcInteractRequest" );
        if( SmcInteractRequest( m_pSmcConnection, SmDialogNormal, InteractProc, nullptr ) )
            bRet = true;
    }
    return bRet;
}

void SessionManagerClient::interactionDone( bool bCancelShutdown )
{
    SAL_INFO("vcl.sm", "SessionManagerClient::interactionDone");

    if( m_pSmcConnection )
    {
        assert(m_xICEConnectionObserver);
        osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
        SAL_INFO("vcl.sm.debug", "  SmcInteractDone = " << (bCancelShutdown ? "true" : "false") );
        SmcInteractDone( m_pSmcConnection, bCancelShutdown ? True : False );
    }
}

OUString SessionManagerClient::getExecName()
{
    SAL_INFO("vcl.sm", "SessionManagerClient::getExecName");

    OUString aExec, aSysExec;
    osl_getExecutableFile( &aExec.pData );
    osl_getSystemPathFromFileURL( aExec.pData, &aSysExec.pData );

    if( aSysExec.endsWith(".bin") )
        aSysExec = aSysExec.copy( 0, aSysExec.getLength() - RTL_CONSTASCII_LENGTH(".bin") );

    SAL_INFO("vcl.sm.debug", "  aSysExec = " << aSysExec);
    return aSysExec;
}

OString SessionManagerClient::getPreviousSessionID()
{
    SAL_INFO("vcl.sm", "SessionManagerClient::getPreviousSessionID");

    OString aPrevId;

    sal_uInt32 n = rtl_getAppCommandArgCount();
    for (sal_uInt32 i = 0; i != n; ++i)
    {
        OUString aArg;
        rtl_getAppCommandArg( i, &aArg.pData );
        if(aArg.match("--session="))
        {
            aPrevId = OUStringToOString(
                aArg.subView(RTL_CONSTASCII_LENGTH("--session=")),
                osl_getThreadTextEncoding());
            break;
        }
    }

    SAL_INFO("vcl.sm.debug", "  previous ID = " << aPrevId);
    return aPrevId;
}

void ICEConnectionObserver::activate()
{
    SAL_INFO("vcl.sm", "ICEConnectionObserver::activate");

    /*
     * Default handlers call exit, we don't care that strongly if something
     * happens to fail
     */
    m_origIOErrorHandler = IceSetIOErrorHandler( IgnoreIceIOErrors );
    m_origErrorHandler = IceSetErrorHandler( IgnoreIceErrors );
    IceAddConnectionWatch( ICEWatchProc, this );
}

void ICEConnectionObserver::deactivate()
{
    SAL_INFO("vcl.sm", "ICEConnectionObserver::deactivate");

    oslThread t;
    {
        osl::MutexGuard g(m_ICEMutex);
        IceRemoveConnectionWatch( ICEWatchProc, this );
        IceSetErrorHandler( m_origErrorHandler );
        IceSetIOErrorHandler( m_origIOErrorHandler );
        m_nConnections = 0;
        t = m_ICEThread;
        m_ICEThread = nullptr;
    }
    if (t)
    {
        SAL_INFO("vcl.sm.debug", "  terminate");
        terminate(t);
    }
}

void ICEConnectionObserver::wakeup()
{
    SAL_INFO("vcl.sm", "ICEConnectionObserver::wakeup");

    char cChar = 'w';
    OSL_VERIFY(write(m_nWakeupFiles[1], &cChar, 1) == 1);
}

void ICEConnectionObserver::terminate(oslThread iceThread)
{
    SAL_INFO("vcl.sm", "ICEConnectionObserver::terminate");

    osl_terminateThread(iceThread);
    wakeup();
    osl_joinWithThread(iceThread);
    osl_destroyThread(iceThread);
    close(m_nWakeupFiles[1]);
    close(m_nWakeupFiles[0]);
}

void ICEConnectionWorker(void * data)
{
    SAL_INFO("vcl.sm", "ICEConnectionWorker");

    osl::Thread::setName("ICEConnectionWorker");
    ICEConnectionObserver * pThis = static_cast< ICEConnectionObserver * >(
        data);
    for (;;)
    {
        oslThread t;
        {
            osl::MutexGuard g(pThis->m_ICEMutex);
            if (pThis->m_ICEThread == nullptr || pThis->m_nConnections == 0)
            {
                break;
            }
            t = pThis->m_ICEThread;
        }
        if (!osl_scheduleThread(t))
        {
            break;
        }

        int nConnectionsBefore;
        struct pollfd* pLocalFD;
        {
            osl::MutexGuard g(pThis->m_ICEMutex);
            nConnectionsBefore = pThis->m_nConnections;
            int nBytes = sizeof( struct pollfd )*(nConnectionsBefore+1);
            pLocalFD = static_cast<struct pollfd*>(std::malloc( nBytes ));
            memcpy( pLocalFD, pThis->m_pFilehandles, nBytes );
        }

        int nRet = poll( pLocalFD,nConnectionsBefore+1,-1 );
        bool bWakeup = (pLocalFD[0].revents & POLLIN);
        std::free( pLocalFD );

        if( nRet < 1 )
            continue;

        // clear wakeup pipe
        if( bWakeup )
        {
            char buf[4];
            while( read( pThis->m_nWakeupFiles[0], buf, sizeof( buf ) ) > 0 )
                ;
            SAL_INFO("vcl.sm.debug", "  file handles active in wakeup: " << nRet);
            if( nRet == 1 )
                continue;
        }

        // check fd's after we obtained the lock
        osl::MutexGuard g(pThis->m_ICEMutex);
        if( pThis->m_nConnections > 0 && pThis->m_nConnections == nConnectionsBefore )
        {
            nRet = poll( pThis->m_pFilehandles+1, pThis->m_nConnections, 0 );
            if( nRet > 0 )
            {
                SAL_INFO("vcl.sm.debug", "  IceProcessMessages");
                Bool bReply;
                for( int i = 0; i < pThis->m_nConnections; i++ )
                    if( pThis->m_pFilehandles[i+1].revents & POLLIN )
                        IceProcessMessages( pThis->m_pConnections[i], nullptr, &bReply );
            }
        }
    }

    SAL_INFO("vcl.sm.debug", "  shutting down ICE dispatch thread");
}

void ICEWatchProc(
    IceConn ice_conn, IcePointer client_data, Bool opening,
    SAL_UNUSED_PARAMETER IcePointer *)
{
    SAL_INFO("vcl.sm", "ICEWatchProc");

    // Note: This is a callback function for ICE; this implicitly means that a
    // call into ICE lib is calling this, so the m_ICEMutex MUST already be
    // locked by the caller.
    ICEConnectionObserver * pThis = static_cast< ICEConnectionObserver * >(
        client_data);
    if( opening )
    {
        SAL_INFO("vcl.sm.debug", "  opening");
        int fd = IceConnectionNumber( ice_conn );
        pThis->m_nConnections++;
        pThis->m_pConnections = static_cast<IceConn*>(std::realloc( pThis->m_pConnections, sizeof( IceConn )*pThis->m_nConnections ));
        pThis->m_pFilehandles = static_cast<struct pollfd*>(std::realloc( pThis->m_pFilehandles, sizeof( struct pollfd )*(pThis->m_nConnections+1) ));
        pThis->m_pConnections[ pThis->m_nConnections-1 ]      = ice_conn;
        pThis->m_pFilehandles[ pThis->m_nConnections ].fd     = fd;
        pThis->m_pFilehandles[ pThis->m_nConnections ].events = POLLIN;
        if( pThis->m_nConnections == 1 )
        {
            SAL_INFO("vcl.sm.debug", "  First connection");
            if (!pipe(pThis->m_nWakeupFiles))
            {
                int flags;
                pThis->m_pFilehandles[0].fd      = pThis->m_nWakeupFiles[0];
                pThis->m_pFilehandles[0].events  = POLLIN;
                // set close-on-exec and nonblock descriptor flag.
                if ((flags = fcntl(pThis->m_nWakeupFiles[0], F_GETFD)) != -1)
                {
                    flags |= FD_CLOEXEC;
                    (void)fcntl(pThis->m_nWakeupFiles[0], F_SETFD, flags);
                }
                if ((flags = fcntl(pThis->m_nWakeupFiles[0], F_GETFL)) != -1)
                {
                    flags |= O_NONBLOCK;
                    (void)fcntl(pThis->m_nWakeupFiles[0], F_SETFL, flags);
                }
                // set close-on-exec and nonblock descriptor flag.
                if ((flags = fcntl(pThis->m_nWakeupFiles[1], F_GETFD)) != -1)
                {
                    flags |= FD_CLOEXEC;
                    (void)fcntl(pThis->m_nWakeupFiles[1], F_SETFD, flags);
                }
                if ((flags = fcntl(pThis->m_nWakeupFiles[1], F_GETFL)) != -1)
                {
                    flags |= O_NONBLOCK;
                    (void)fcntl(pThis->m_nWakeupFiles[1], F_SETFL, flags);
                }
                pThis->m_ICEThread = osl_createThread(
                    ICEConnectionWorker, pThis);
            }
        }
    }
    else // closing
    {
        SAL_INFO("vcl.sm.debug", "  closing");
        for( int i = 0; i < pThis->m_nConnections; i++ )
        {
            if( pThis->m_pConnections[i] == ice_conn )
            {
                if( i < pThis->m_nConnections-1 )
                {
                    memmove( pThis->m_pConnections+i, pThis->m_pConnections+i+1, sizeof( IceConn )*(pThis->m_nConnections-i-1) );
                    memmove( pThis->m_pFilehandles+i+1, pThis->m_pFilehandles+i+2, sizeof( struct pollfd )*(pThis->m_nConnections-i-1) );
                }
                pThis->m_nConnections--;
                pThis->m_pConnections = static_cast<IceConn*>(std::realloc( pThis->m_pConnections, sizeof( IceConn )*pThis->m_nConnections ));
                pThis->m_pFilehandles = static_cast<struct pollfd*>(std::realloc( pThis->m_pFilehandles, sizeof( struct pollfd )*(pThis->m_nConnections+1) ));
                break;
            }
        }
        if( pThis->m_nConnections == 0 && pThis->m_ICEThread )
        {
            SAL_INFO("vcl.sm.debug", "  terminating ICEThread");
            oslThread t = pThis->m_ICEThread;
            pThis->m_ICEThread = nullptr;

            // must release the mutex here
            pThis->m_ICEMutex.release();

            pThis->terminate(t);

            // acquire the mutex again, because the caller does not expect
            // it to be released when calling into SM
            pThis->m_ICEMutex.acquire();
        }
    }

    SAL_INFO( "vcl.sm.debug", "  ICE connection     on " << IceConnectionNumber( ice_conn ) );
    SAL_INFO( "vcl.sm.debug", "  Display connection is " << ConnectionNumber( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() ) );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
