<commit_before>/* -*- mode: c++; c-basic-offset:4 -*-
    commands/lookupcertificatescommand.cpp

    This file is part of Kleopatra, the KDE keymanager
    Copyright (c) 2008 Klarälvdalens Datakonsult AB

    Kleopatra is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Kleopatra is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

    In addition, as a special exception, the copyright holders give
    permission to link the code of this program with any edition of
    the Qt library by Trolltech AS, Norway (or with modified versions
    of Qt that use the same license as Qt), and distribute linked
    combinations including the two.  You must obey the GNU General
    Public License in all respects for all of the code used other than
    Qt.  If you modify this file, you may extend this exception to
    your version of the file, but you are not obligated to do so.  If
    you do not wish to do so, delete this exception statement from
    your version.
*/

#include <config-kleopatra.h>

#include "lookupcertificatescommand.h"

#include "importcertificatescommand_p.h"

#include "detailscommand.h"

#include <dialogs/lookupcertificatesdialog.h>

#include <utils/stl_util.h>
#include <utils/formatting.h>

#include <kleo/downloadjob.h>
#include <kleo/importjob.h>
#include <kleo/keylistjob.h>
#include <kleo/cryptobackendfactory.h>
#include <kleo/cryptobackend.h>
#include <kleo/cryptoconfig.h>

#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>

#include <KLocale>
#include <KMessageBox>
#include <kdebug.h>

#include <QBuffer>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include <vector>
#include <map>
#include <algorithm>
#include <cassert>

using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
using namespace boost;

class LookupCertificatesCommand::Private : public ImportCertificatesCommand::Private {
    friend class ::Kleo::Commands::LookupCertificatesCommand;
    LookupCertificatesCommand * q_func() const { return static_cast<LookupCertificatesCommand*>( q ); }
public:
    explicit Private( LookupCertificatesCommand * qq, KeyListController * c );
    ~Private();

    void init();

private:
    void slotSearchTextChanged( const QString & str );
    void slotNextKey( const Key & key ) {
        keyListing.keys.push_back( key );
    }
    void slotKeyListResult( const KeyListResult & result );
    void slotImportRequested( const std::vector<Key> & keys );
    void slotOpenPGPDownloadResult( const Error & err, const QByteArray & data );
    void slotCMSDownloadResult( const Error & err, const QByteArray & data );
    void slotDetailsRequested( const Key & key );
    void slotSaveAsRequested( const std::vector<Key> & keys );
    void slotDialogRejected() {
        canceled();
    }

private:
    using ImportCertificatesCommand::Private::showError;
    void showError( QWidget * parent, const KeyListResult & result );
    void createDialog();
    KeyListJob * createKeyListJob( GpgME::Protocol proto ) const {
        const CryptoBackend::Protocol * const cbp = CryptoBackendFactory::instance()->protocol( proto );
        return cbp ? cbp->keyListJob( true ) : 0 ;
    }
    DownloadJob * createDownloadJob( GpgME::Protocol proto ) const {
        const CryptoBackend::Protocol * const cbp = CryptoBackendFactory::instance()->protocol( proto );
        return cbp ? cbp->downloadJob() : 0 ;
    }
    void startKeyListJob( GpgME::Protocol proto, const QString & str );
    void startDownloadJob( const Key & key );
    void checkForDownloadFinished();
    bool checkConfig() const;

    QWidget * dialogOrView() const { if ( dialog ) return dialog; else return view(); }

private:
    QPointer<LookupCertificatesDialog> dialog;
    struct KeyListingVariables {
        QPointer<KeyListJob> cms, openpgp;
        KeyListResult result;
        std::vector<Key> keys;

        void reset() { *this = KeyListingVariables(); }
    } keyListing;
    struct DownloadVariables {
        Key key;
        QPointer<DownloadJob> job;
        Error error;
    };
    std::vector<DownloadVariables> downloads;
    QByteArray openPGPDownloadData, cmsDownloadData;
};

LookupCertificatesCommand::Private * LookupCertificatesCommand::d_func() { return static_cast<Private*>( d.get() ); }
const LookupCertificatesCommand::Private * LookupCertificatesCommand::d_func() const { return static_cast<const Private*>( d.get() ); }

#define d d_func()
#define q q_func()

LookupCertificatesCommand::Private::Private( LookupCertificatesCommand * qq, KeyListController * c )
    : ImportCertificatesCommand::Private( qq, c ),
      dialog()
{

}

LookupCertificatesCommand::Private::~Private() {
    kDebug();
    delete dialog;
}

LookupCertificatesCommand::LookupCertificatesCommand( KeyListController * c )
    : ImportCertificatesCommand( new Private( this, c ) )
{
    d->init();
}

LookupCertificatesCommand::LookupCertificatesCommand( QAbstractItemView * v, KeyListController * c )
    : ImportCertificatesCommand( v, new Private( this, c ) )
{
    d->init();
}

void LookupCertificatesCommand::Private::init() {

}

LookupCertificatesCommand::~LookupCertificatesCommand() { kDebug(); }

void LookupCertificatesCommand::doStart() {

    if ( !d->checkConfig() ) {
        d->finished();
        return;
    }

    d->createDialog();
    assert( d->dialog );

    d->dialog->setPassive( false );
    d->dialog->show();

}

void LookupCertificatesCommand::Private::createDialog() {
    if ( dialog )
        return;
    dialog = new LookupCertificatesDialog( view() );
    dialog->setAttribute( Qt::WA_DeleteOnClose );
    connect( dialog, SIGNAL(searchTextChanged(QString)),
             q, SLOT(slotSearchTextChanged(QString)) );
    connect( dialog, SIGNAL(saveAsRequested(std::vector<GpgME::Key>)),
             q, SLOT(slotSaveAsRequested(std::vector<GpgME::Key>)) );
    connect( dialog, SIGNAL(importRequested(std::vector<GpgME::Key>)),
             q, SLOT(slotImportRequested(std::vector<GpgME::Key>)) );
    connect( dialog, SIGNAL(detailsRequested(GpgME::Key)),
             q, SLOT(slotDetailsRequested(GpgME::Key)) );
    connect( dialog, SIGNAL(rejected()),
             q, SLOT(slotDialogRejected()) );
}

void LookupCertificatesCommand::Private::slotSearchTextChanged( const QString & str ) {
    dialog->setPassive( true );

    startKeyListJob( CMS,     str );
    startKeyListJob( OpenPGP, str );

}

void LookupCertificatesCommand::Private::startKeyListJob( GpgME::Protocol proto, const QString & str ) {
    KeyListJob * const klj = createKeyListJob( proto );
    if ( !klj )
        return;
    connect( klj, SIGNAL(result(GpgME::KeyListResult)),
             q, SLOT(slotKeyListResult(GpgME::KeyListResult)) );
    connect( klj, SIGNAL(nextKey(GpgME::Key)),
             q, SLOT(slotNextKey(GpgME::Key)) );
    if ( const Error err = klj->start( QStringList( str ) ) )
        keyListing.result.mergeWith( KeyListResult( err ) );
    else
        if ( proto == CMS )
            keyListing.cms     = klj;
        else
            keyListing.openpgp = klj;
}

void LookupCertificatesCommand::Private::slotKeyListResult( const KeyListResult & r ) {

    if ( q->sender() == keyListing.cms )
        keyListing.cms = 0;
    else if ( q->sender() == keyListing.openpgp )
        keyListing.openpgp = 0;
    else
        kDebug() << "unknown sender()" << q->sender();

    keyListing.result.mergeWith( r );
    if ( keyListing.cms || keyListing.openpgp ) // still waiting for jobs to complete
        return;

    if ( keyListing.result.error() && !keyListing.result.error().isCanceled() )
        showError( dialogOrView(), keyListing.result );

    if ( dialog ) {
        dialog->setCertificates( keyListing.keys );
        dialog->setPassive( false );
    } else {
        finished();
    }

    keyListing.reset();
}

void LookupCertificatesCommand::Private::slotImportRequested( const std::vector<Key> & keys ) {
    dialog = 0;

    assert( !keys.empty() );
    kdtools::for_each( keys, bind( &Private::startDownloadJob, this, _1 ) );
}

static shared_ptr<QBuffer> make_open_qbuffer() {
    const shared_ptr<QBuffer> buffer( new QBuffer );
    if ( !buffer->open( QIODevice::WriteOnly ) )
        kFatal() << "QBuffer::open failed";
    return buffer;
}

void LookupCertificatesCommand::Private::startDownloadJob( const Key & key ) {
    if ( key.isNull() )
        return;
    DownloadJob * const dlj = createDownloadJob( key.protocol() );
    if ( !dlj )
        return;
    connect( dlj, SIGNAL(result(GpgME::Error,QByteArray)),
             q, key.protocol() == CMS
             ? SLOT(slotCMSDownloadResult(GpgME::Error,QByteArray))
             : SLOT(slotOpenPGPDownloadResult(GpgME::Error,QByteArray)) );
    DownloadVariables var;
    var.key = key;
    if ( const Error err = dlj->start( QStringList( key.primaryFingerprint() ) ) )
        var.error = err;
    else
        var.job = dlj;
    downloads.push_back( var );
}

namespace {
    template <typename T>
    QStringList filter_and_format_successful_downloads( const T & t ) {
        QStringList result;

        return result;
    }

    template <typename T>
    QByteArray combined_nonfailed_data( GpgME::Protocol proto, const T & t ) {
        QByteArray result;
        Q_FOREACH( const typename T::value_type & v, t )
            if ( !v.error.code() && v.data && v.key.protocol() == proto )
                result.append( v.data->data() );
        return result;
    }
}

void LookupCertificatesCommand::Private::slotOpenPGPDownloadResult( const GpgME::Error & err, const QByteArray & keyData ) {
    const std::vector<DownloadVariables>::iterator it =
        std::find_if( downloads.begin(), downloads.end(),
                      bind( &DownloadVariables::job, _1 ) == q->sender() );
    assert( it != downloads.end() );

    openPGPDownloadData.append( keyData );

    it->job = 0;
    it->error = err;

    checkForDownloadFinished();
}

void LookupCertificatesCommand::Private::slotCMSDownloadResult( const GpgME::Error & err, const QByteArray & keyData ) {
    const std::vector<DownloadVariables>::iterator it =
        std::find_if( downloads.begin(), downloads.end(),
                      bind( &DownloadVariables::job, _1 ) == q->sender() );
    assert( it != downloads.end() );

    cmsDownloadData.append( keyData );

    it->job = 0;
    it->error = err;

    checkForDownloadFinished();
}

void LookupCertificatesCommand::Private::checkForDownloadFinished() {

    if ( kdtools::any( downloads, mem_fn( &DownloadVariables::job ) ) )
        return; // still jobs to end

    if ( kdtools::all( downloads, mem_fn( &DownloadVariables::error ) ) ) {
        KMessageBox::information( dialogOrView(),
                                  downloads.size() == 1 ?
                                  i18n( "Download of certificate %1 failed. Error message: %2",
                                        Formatting::formatForComboBox( downloads.front().key ),
                                        QString::fromLocal8Bit( downloads.front().error.asString() ) ) :
                                  i18n( "All certificate downloads failed. Sample error message: %1",
                                        QString::fromLocal8Bit( downloads.front().error.asString() ) ),
                                  i18n( "Certificate Downloads Failed" ) );
        finished();
        return;
    } else if ( kdtools::any( downloads, mem_fn( &DownloadVariables::error ) ) &&
                KMessageBox::questionYesNoList( dialogOrView(),
                                                i18n( "Some certificates failed to download. "
                                                      "Do you want to proceed importing the following, succeded, downloads?" ),
                                                filter_and_format_successful_downloads( downloads ),
                                                i18n( "Certificate Downloads Failed" ) )
                == KMessageBox::No )
    {
        finished();
        return;
    }

    const QByteArray cms = cmsDownloadData;//combined_nonfailed_data( CMS,     downloads );
    const QByteArray pgp = openPGPDownloadData;//combined_nonfailed_data( OpenPGP, downloads );

    if ( !cms.isEmpty() )
        startImport( CMS,     cms );
    if ( !pgp.isEmpty() )
        startImport( OpenPGP, pgp );
}

void LookupCertificatesCommand::Private::slotSaveAsRequested( const std::vector<Key> & keys ) {
    kDebug() << "not implemented";
}

void LookupCertificatesCommand::Private::slotDetailsRequested( const Key & key ) {
    ( new DetailsCommand( key, view(), controller() ) )->start();
}

void LookupCertificatesCommand::doCancel() {
    ImportCertificatesCommand::doCancel();
    if ( QDialog * const dlg = d->dialog ) {
        d->dialog = 0;
        dlg->close();
    }
}

void LookupCertificatesCommand::Private::showError( QWidget * parent, const KeyListResult & result ) {
    if ( !result.error() )
        return;
    KMessageBox::information( parent, i18n( "Failed to search on keyserver. The error returned was:\n%1",
                                            QString::fromLocal8Bit( result.error().asString() ) ) );
}

static bool haveOpenPGPKeyserverConfigured() {
    const Kleo::CryptoConfig * const config = Kleo::CryptoBackendFactory::instance()->config();
    if ( !config )
        return false;
    const Kleo::CryptoConfigEntry * const entry = config->entry( "gpg", "Keyserver", "keyserver" );
    return entry && !entry->stringValue().isEmpty();
}

static bool haveX509DirectoryServerConfigured() {
    const Kleo::CryptoConfig * const config = Kleo::CryptoBackendFactory::instance()->config();
    if ( !config )
        return false;
    const Kleo::CryptoConfigEntry * const entry = config->entry( "dirmngr", "LDAP", "LDAP Server" );
    return entry && !entry->urlValueList().empty();
}

bool LookupCertificatesCommand::Private::checkConfig() const {
    const bool ok = haveOpenPGPKeyserverConfigured() || haveX509DirectoryServerConfigured();
    if ( !ok )
        KMessageBox::information( view(), i18nc("@info",
                                           "<para>You do not have any directory servers configured.</para>"
                                           "<para>You need to configure at least one directory server to "
                                           "search on one.</para>"
                                           "<para>You can configure directory servers here: "
                                           "<interface>Settings->Configure Kleopatra</interface>.</para>"),
                                  i18nc("@title", "No Directory Servers Configured") );
    return ok;
}

#undef d
#undef q

#include "moc_lookupcertificatescommand.cpp"
<commit_msg>Clear the list in the dialog when starting search.<commit_after>/* -*- mode: c++; c-basic-offset:4 -*-
    commands/lookupcertificatescommand.cpp

    This file is part of Kleopatra, the KDE keymanager
    Copyright (c) 2008 Klarälvdalens Datakonsult AB

    Kleopatra is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Kleopatra is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

    In addition, as a special exception, the copyright holders give
    permission to link the code of this program with any edition of
    the Qt library by Trolltech AS, Norway (or with modified versions
    of Qt that use the same license as Qt), and distribute linked
    combinations including the two.  You must obey the GNU General
    Public License in all respects for all of the code used other than
    Qt.  If you modify this file, you may extend this exception to
    your version of the file, but you are not obligated to do so.  If
    you do not wish to do so, delete this exception statement from
    your version.
*/

#include <config-kleopatra.h>

#include "lookupcertificatescommand.h"

#include "importcertificatescommand_p.h"

#include "detailscommand.h"

#include <dialogs/lookupcertificatesdialog.h>

#include <utils/stl_util.h>
#include <utils/formatting.h>

#include <kleo/downloadjob.h>
#include <kleo/importjob.h>
#include <kleo/keylistjob.h>
#include <kleo/cryptobackendfactory.h>
#include <kleo/cryptobackend.h>
#include <kleo/cryptoconfig.h>

#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>

#include <KLocale>
#include <KMessageBox>
#include <kdebug.h>

#include <QBuffer>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include <vector>
#include <map>
#include <algorithm>
#include <cassert>

using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
using namespace boost;

class LookupCertificatesCommand::Private : public ImportCertificatesCommand::Private {
    friend class ::Kleo::Commands::LookupCertificatesCommand;
    LookupCertificatesCommand * q_func() const { return static_cast<LookupCertificatesCommand*>( q ); }
public:
    explicit Private( LookupCertificatesCommand * qq, KeyListController * c );
    ~Private();

    void init();

private:
    void slotSearchTextChanged( const QString & str );
    void slotNextKey( const Key & key ) {
        keyListing.keys.push_back( key );
    }
    void slotKeyListResult( const KeyListResult & result );
    void slotImportRequested( const std::vector<Key> & keys );
    void slotOpenPGPDownloadResult( const Error & err, const QByteArray & data );
    void slotCMSDownloadResult( const Error & err, const QByteArray & data );
    void slotDetailsRequested( const Key & key );
    void slotSaveAsRequested( const std::vector<Key> & keys );
    void slotDialogRejected() {
        canceled();
    }

private:
    using ImportCertificatesCommand::Private::showError;
    void showError( QWidget * parent, const KeyListResult & result );
    void createDialog();
    KeyListJob * createKeyListJob( GpgME::Protocol proto ) const {
        const CryptoBackend::Protocol * const cbp = CryptoBackendFactory::instance()->protocol( proto );
        return cbp ? cbp->keyListJob( true ) : 0 ;
    }
    DownloadJob * createDownloadJob( GpgME::Protocol proto ) const {
        const CryptoBackend::Protocol * const cbp = CryptoBackendFactory::instance()->protocol( proto );
        return cbp ? cbp->downloadJob() : 0 ;
    }
    void startKeyListJob( GpgME::Protocol proto, const QString & str );
    void startDownloadJob( const Key & key );
    void checkForDownloadFinished();
    bool checkConfig() const;

    QWidget * dialogOrView() const { if ( dialog ) return dialog; else return view(); }

private:
    QPointer<LookupCertificatesDialog> dialog;
    struct KeyListingVariables {
        QPointer<KeyListJob> cms, openpgp;
        KeyListResult result;
        std::vector<Key> keys;

        void reset() { *this = KeyListingVariables(); }
    } keyListing;
    struct DownloadVariables {
        Key key;
        QPointer<DownloadJob> job;
        Error error;
    };
    std::vector<DownloadVariables> downloads;
    QByteArray openPGPDownloadData, cmsDownloadData;
};

LookupCertificatesCommand::Private * LookupCertificatesCommand::d_func() { return static_cast<Private*>( d.get() ); }
const LookupCertificatesCommand::Private * LookupCertificatesCommand::d_func() const { return static_cast<const Private*>( d.get() ); }

#define d d_func()
#define q q_func()

LookupCertificatesCommand::Private::Private( LookupCertificatesCommand * qq, KeyListController * c )
    : ImportCertificatesCommand::Private( qq, c ),
      dialog()
{

}

LookupCertificatesCommand::Private::~Private() {
    kDebug();
    delete dialog;
}

LookupCertificatesCommand::LookupCertificatesCommand( KeyListController * c )
    : ImportCertificatesCommand( new Private( this, c ) )
{
    d->init();
}

LookupCertificatesCommand::LookupCertificatesCommand( QAbstractItemView * v, KeyListController * c )
    : ImportCertificatesCommand( v, new Private( this, c ) )
{
    d->init();
}

void LookupCertificatesCommand::Private::init() {

}

LookupCertificatesCommand::~LookupCertificatesCommand() { kDebug(); }

void LookupCertificatesCommand::doStart() {

    if ( !d->checkConfig() ) {
        d->finished();
        return;
    }

    d->createDialog();
    assert( d->dialog );

    d->dialog->setPassive( false );
    d->dialog->show();

}

void LookupCertificatesCommand::Private::createDialog() {
    if ( dialog )
        return;
    dialog = new LookupCertificatesDialog( view() );
    dialog->setAttribute( Qt::WA_DeleteOnClose );
    connect( dialog, SIGNAL(searchTextChanged(QString)),
             q, SLOT(slotSearchTextChanged(QString)) );
    connect( dialog, SIGNAL(saveAsRequested(std::vector<GpgME::Key>)),
             q, SLOT(slotSaveAsRequested(std::vector<GpgME::Key>)) );
    connect( dialog, SIGNAL(importRequested(std::vector<GpgME::Key>)),
             q, SLOT(slotImportRequested(std::vector<GpgME::Key>)) );
    connect( dialog, SIGNAL(detailsRequested(GpgME::Key)),
             q, SLOT(slotDetailsRequested(GpgME::Key)) );
    connect( dialog, SIGNAL(rejected()),
             q, SLOT(slotDialogRejected()) );
}

void LookupCertificatesCommand::Private::slotSearchTextChanged( const QString & str ) {
    dialog->setPassive( true );
    dialog->setCertificates( std::vector<Key>() );

    startKeyListJob( CMS,     str );
    startKeyListJob( OpenPGP, str );

}

void LookupCertificatesCommand::Private::startKeyListJob( GpgME::Protocol proto, const QString & str ) {
    KeyListJob * const klj = createKeyListJob( proto );
    if ( !klj )
        return;
    connect( klj, SIGNAL(result(GpgME::KeyListResult)),
             q, SLOT(slotKeyListResult(GpgME::KeyListResult)) );
    connect( klj, SIGNAL(nextKey(GpgME::Key)),
             q, SLOT(slotNextKey(GpgME::Key)) );
    if ( const Error err = klj->start( QStringList( str ) ) )
        keyListing.result.mergeWith( KeyListResult( err ) );
    else
        if ( proto == CMS )
            keyListing.cms     = klj;
        else
            keyListing.openpgp = klj;
}

void LookupCertificatesCommand::Private::slotKeyListResult( const KeyListResult & r ) {

    if ( q->sender() == keyListing.cms )
        keyListing.cms = 0;
    else if ( q->sender() == keyListing.openpgp )
        keyListing.openpgp = 0;
    else
        kDebug() << "unknown sender()" << q->sender();

    keyListing.result.mergeWith( r );
    if ( keyListing.cms || keyListing.openpgp ) // still waiting for jobs to complete
        return;

    if ( keyListing.result.error() && !keyListing.result.error().isCanceled() )
        showError( dialogOrView(), keyListing.result );

    if ( dialog ) {
        dialog->setCertificates( keyListing.keys );
        dialog->setPassive( false );
    } else {
        finished();
    }

    keyListing.reset();
}

void LookupCertificatesCommand::Private::slotImportRequested( const std::vector<Key> & keys ) {
    dialog = 0;

    assert( !keys.empty() );
    kdtools::for_each( keys, bind( &Private::startDownloadJob, this, _1 ) );
}

static shared_ptr<QBuffer> make_open_qbuffer() {
    const shared_ptr<QBuffer> buffer( new QBuffer );
    if ( !buffer->open( QIODevice::WriteOnly ) )
        kFatal() << "QBuffer::open failed";
    return buffer;
}

void LookupCertificatesCommand::Private::startDownloadJob( const Key & key ) {
    if ( key.isNull() )
        return;
    DownloadJob * const dlj = createDownloadJob( key.protocol() );
    if ( !dlj )
        return;
    connect( dlj, SIGNAL(result(GpgME::Error,QByteArray)),
             q, key.protocol() == CMS
             ? SLOT(slotCMSDownloadResult(GpgME::Error,QByteArray))
             : SLOT(slotOpenPGPDownloadResult(GpgME::Error,QByteArray)) );
    DownloadVariables var;
    var.key = key;
    if ( const Error err = dlj->start( QStringList( key.primaryFingerprint() ) ) )
        var.error = err;
    else
        var.job = dlj;
    downloads.push_back( var );
}

namespace {
    template <typename T>
    QStringList filter_and_format_successful_downloads( const T & t ) {
        QStringList result;

        return result;
    }

    template <typename T>
    QByteArray combined_nonfailed_data( GpgME::Protocol proto, const T & t ) {
        QByteArray result;
        Q_FOREACH( const typename T::value_type & v, t )
            if ( !v.error.code() && v.data && v.key.protocol() == proto )
                result.append( v.data->data() );
        return result;
    }
}

void LookupCertificatesCommand::Private::slotOpenPGPDownloadResult( const GpgME::Error & err, const QByteArray & keyData ) {
    const std::vector<DownloadVariables>::iterator it =
        std::find_if( downloads.begin(), downloads.end(),
                      bind( &DownloadVariables::job, _1 ) == q->sender() );
    assert( it != downloads.end() );

    openPGPDownloadData.append( keyData );

    it->job = 0;
    it->error = err;

    checkForDownloadFinished();
}

void LookupCertificatesCommand::Private::slotCMSDownloadResult( const GpgME::Error & err, const QByteArray & keyData ) {
    const std::vector<DownloadVariables>::iterator it =
        std::find_if( downloads.begin(), downloads.end(),
                      bind( &DownloadVariables::job, _1 ) == q->sender() );
    assert( it != downloads.end() );

    cmsDownloadData.append( keyData );

    it->job = 0;
    it->error = err;

    checkForDownloadFinished();
}

void LookupCertificatesCommand::Private::checkForDownloadFinished() {

    if ( kdtools::any( downloads, mem_fn( &DownloadVariables::job ) ) )
        return; // still jobs to end

    if ( kdtools::all( downloads, mem_fn( &DownloadVariables::error ) ) ) {
        KMessageBox::information( dialogOrView(),
                                  downloads.size() == 1 ?
                                  i18n( "Download of certificate %1 failed. Error message: %2",
                                        Formatting::formatForComboBox( downloads.front().key ),
                                        QString::fromLocal8Bit( downloads.front().error.asString() ) ) :
                                  i18n( "All certificate downloads failed. Sample error message: %1",
                                        QString::fromLocal8Bit( downloads.front().error.asString() ) ),
                                  i18n( "Certificate Downloads Failed" ) );
        finished();
        return;
    } else if ( kdtools::any( downloads, mem_fn( &DownloadVariables::error ) ) &&
                KMessageBox::questionYesNoList( dialogOrView(),
                                                i18n( "Some certificates failed to download. "
                                                      "Do you want to proceed importing the following, succeded, downloads?" ),
                                                filter_and_format_successful_downloads( downloads ),
                                                i18n( "Certificate Downloads Failed" ) )
                == KMessageBox::No )
    {
        finished();
        return;
    }

    const QByteArray cms = cmsDownloadData;//combined_nonfailed_data( CMS,     downloads );
    const QByteArray pgp = openPGPDownloadData;//combined_nonfailed_data( OpenPGP, downloads );

    if ( !cms.isEmpty() )
        startImport( CMS,     cms );
    if ( !pgp.isEmpty() )
        startImport( OpenPGP, pgp );
}

void LookupCertificatesCommand::Private::slotSaveAsRequested( const std::vector<Key> & keys ) {
    kDebug() << "not implemented";
}

void LookupCertificatesCommand::Private::slotDetailsRequested( const Key & key ) {
    ( new DetailsCommand( key, view(), controller() ) )->start();
}

void LookupCertificatesCommand::doCancel() {
    ImportCertificatesCommand::doCancel();
    if ( QDialog * const dlg = d->dialog ) {
        d->dialog = 0;
        dlg->close();
    }
}

void LookupCertificatesCommand::Private::showError( QWidget * parent, const KeyListResult & result ) {
    if ( !result.error() )
        return;
    KMessageBox::information( parent, i18n( "Failed to search on keyserver. The error returned was:\n%1",
                                            QString::fromLocal8Bit( result.error().asString() ) ) );
}

static bool haveOpenPGPKeyserverConfigured() {
    const Kleo::CryptoConfig * const config = Kleo::CryptoBackendFactory::instance()->config();
    if ( !config )
        return false;
    const Kleo::CryptoConfigEntry * const entry = config->entry( "gpg", "Keyserver", "keyserver" );
    return entry && !entry->stringValue().isEmpty();
}

static bool haveX509DirectoryServerConfigured() {
    const Kleo::CryptoConfig * const config = Kleo::CryptoBackendFactory::instance()->config();
    if ( !config )
        return false;
    const Kleo::CryptoConfigEntry * const entry = config->entry( "dirmngr", "LDAP", "LDAP Server" );
    return entry && !entry->urlValueList().empty();
}

bool LookupCertificatesCommand::Private::checkConfig() const {
    const bool ok = haveOpenPGPKeyserverConfigured() || haveX509DirectoryServerConfigured();
    if ( !ok )
        KMessageBox::information( view(), i18nc("@info",
                                           "<para>You do not have any directory servers configured.</para>"
                                           "<para>You need to configure at least one directory server to "
                                           "search on one.</para>"
                                           "<para>You can configure directory servers here: "
                                           "<interface>Settings->Configure Kleopatra</interface>.</para>"),
                                  i18nc("@title", "No Directory Servers Configured") );
    return ok;
}

#undef d
#undef q

#include "moc_lookupcertificatescommand.cpp"
<|endoftext|>


<commit_before>#include "intfunction.h"
#include "exprcalc.cpp"
#include <cstring>

const size_t MAX_SIZE = 1000;

void IntFunction::copy(const IntFunction& other)
{
    name = other.name;
    param = other.param;

    expr = new char[strlen(other.expr) + 1];
    strcpy(expr, other.expr);
}

void IntFunction::destroy()
{
    delete[] expr;
}

bool IntFunction::parse(const char* dfn)
{
    // първо извличаме името (латинска буква)
    const char* i = find_token(dfn);
    if (!i || !is_alpha(*i))
        return false;
    name = *i;

    // после извличаме '('
    i = find_token(i);
    if (!i || *i != '(')
        return false;

    // след това извличаме името на аргумента
    i = find_token(i);
    if (!i || !is_alpha(*i))
        return false;
    param = *i;

    // после извличаме ')'
    i = find_token(i);
    if (!i || *i != ')')
        return false;

    // след това извличаме '='
    i = find_token(i);
    if (!i || *i != '=')
        return false;

    // и накрая копираме израза в полето expr
    i = find_token(i);
    expr = new char[strlen(i) + 1];
    strcpy(expr, i);
    return true;
}

IntFunction::IntFunction(std::istream& in)
{
    char strin[MAX_SIZE];
    in.getline(strin, MAX_SIZE);
    parse(strin);
}

IntFunction::IntFunction(const char* dfn)
{
    parse(dfn);
}

IntFunction::IntFunction(const std::string& s)
{
    parse(s.c_str());
}

IntFunction::IntFunction(const IntFunction& other)
{
    copy(other);
}

IntFunction& IntFunction::operator=(const IntFunction& other)
{
    if (&other != this)
    {
        destroy();
        copy(other);
    }

    return *this;
}

IntFunction::~IntFunction()
{
    destroy();
}

int IntFunction::calculate(int arg) const
{
    strsubst(expr, param, arg);
    return calculate_expr(expr);
}
<commit_msg>fixed exprcalc include: source file (.cpp) was included instead of header (.h) file<commit_after>#include "intfunction.h"
#include "exprcalc.h"
#include <cstring>

const size_t MAX_SIZE = 1000;

void IntFunction::copy(const IntFunction& other)
{
    name = other.name;
    param = other.param;

    expr = new char[strlen(other.expr) + 1];
    strcpy(expr, other.expr);
}

void IntFunction::destroy()
{
    delete[] expr;
}

bool IntFunction::parse(const char* dfn)
{
    // първо извличаме името (латинска буква)
    const char* i = find_token(dfn);
    if (!i || !is_alpha(*i))
        return false;
    name = *i;

    // после извличаме '('
    i = find_token(i);
    if (!i || *i != '(')
        return false;

    // след това извличаме името на аргумента
    i = find_token(i);
    if (!i || !is_alpha(*i))
        return false;
    param = *i;

    // после извличаме ')'
    i = find_token(i);
    if (!i || *i != ')')
        return false;

    // след това извличаме '='
    i = find_token(i);
    if (!i || *i != '=')
        return false;

    // и накрая копираме израза в полето expr
    i = find_token(i);
    expr = new char[strlen(i) + 1];
    strcpy(expr, i);
    return true;
}

IntFunction::IntFunction(std::istream& in)
{
    char strin[MAX_SIZE];
    in.getline(strin, MAX_SIZE);
    parse(strin);
}

IntFunction::IntFunction(const char* dfn)
{
    parse(dfn);
}

IntFunction::IntFunction(const std::string& s)
{
    parse(s.c_str());
}

IntFunction::IntFunction(const IntFunction& other)
{
    copy(other);
}

IntFunction& IntFunction::operator=(const IntFunction& other)
{
    if (&other != this)
    {
        destroy();
        copy(other);
    }

    return *this;
}

IntFunction::~IntFunction()
{
    destroy();
}

int IntFunction::calculate(int arg) const
{
    strsubst(expr, param, arg);
    return calculate_expr(expr);
}
<|endoftext|>


<commit_before>#pragma once

#include "apple_hid_usage_tables.hpp"
#include "console_user_client.hpp"
#include "constants.hpp"
#include "hid_report.hpp"
#include "human_interface_device.hpp"
#include "iokit_user_client.hpp"
#include "iokit_utility.hpp"
#include "logger.hpp"
#include "modifier_flag_manager.hpp"
#include "userspace_defs.h"
#include "virtual_hid_manager_user_client_method.hpp"

class event_grabber final {
public:
  event_grabber(void) : iokit_user_client_(logger::get_logger(), "org_pqrs_driver_VirtualHIDManager", kIOHIDServerConnectType),
                        console_user_client_(modifier_flag_manager_) {
    manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
    if (!manager_) {
      return;
    }

    auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries({
        std::make_pair(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard),
        // std::make_pair(kHIDPage_Consumer, kHIDUsage_Csmr_ConsumerControl),
        // std::make_pair(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse),
    });
    if (device_matching_dictionaries) {
      IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
      CFRelease(device_matching_dictionaries);

      IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
      IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

      IOHIDManagerScheduleWithRunLoop(manager_, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    }
  }

  ~event_grabber(void) {
    if (manager_) {
      IOHIDManagerUnscheduleFromRunLoop(manager_, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
      CFRelease(manager_);
      manager_ = nullptr;
    }
  }

private:
  static void static_device_matching_callback(void* _Nullable context, IOReturn result, void* _Nullable sender, IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<event_grabber*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    hids_[device] = std::make_unique<human_interface_device>(device);
    auto& dev = hids_[device];

    std::cout << "matching: " << std::endl
              << "  vendor_id:0x" << std::hex << dev->get_vendor_id() << std::endl
              << "  product_id:0x" << std::hex << dev->get_product_id() << std::endl
              << "  location_id:0x" << std::hex << dev->get_location_id() << std::endl
              << "  serial_number:" << dev->get_serial_number_string() << std::endl
              << "  manufacturer:" << dev->get_manufacturer() << std::endl
              << "  product:" << dev->get_product() << std::endl
              << "  transport:" << dev->get_transport() << std::endl;

    if (dev->get_serial_number_string() == "org.pqrs.driver.VirtualHIDKeyboard") {
      return;
    }

    if (dev->get_manufacturer() != "pqrs.org") {
      if (dev->get_manufacturer() == "Apple Inc.") {
        dev->grab(boost::bind(&event_grabber::value_callback, this, _1, _2, _3, _4, _5));
      }
    }
  }

  static void static_device_removal_callback(void* _Nullable context, IOReturn result, void* _Nullable sender, IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<event_grabber*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    auto it = hids_.find(device);
    if (it != hids_.end()) {
      auto& dev = it->second;
      if (dev) {
        std::cout << "removal vendor_id:0x" << std::hex << dev->get_vendor_id() << " product_id:0x" << std::hex << dev->get_product_id() << std::endl;
        hids_.erase(it);
      }
    }
  }

  void value_callback(IOHIDValueRef _Nonnull value,
                      IOHIDElementRef _Nonnull element,
                      uint32_t usage_page,
                      uint32_t usage,
                      CFIndex integer_value) {

    std::cout << "element" << std::endl
              << "  usage_page:0x" << std::hex << usage_page << std::endl
              << "  usage:0x" << std::hex << usage << std::endl
              << "  type:" << IOHIDElementGetType(element) << std::endl
              << "  length:" << IOHIDValueGetLength(value) << std::endl
              << "  integer_value:" << integer_value << std::endl;

    switch (usage_page) {
    case kHIDPage_KeyboardOrKeypad: {
      bool pressed = integer_value;
      handle_keyboard_event(usage_page, usage, pressed);
      break;
    }

    case kHIDPage_AppleVendorTopCase:
      if (usage == kHIDUsage_AV_TopCase_KeyboardFn) {
        bool pressed = integer_value;
        handle_keyboard_event(usage_page, usage, pressed);
      }
      break;

    default:
      break;
    }
  }

  bool handle_modifier_flag_event(uint32_t usage_page, uint32_t usage, bool pressed) {
    auto operation = pressed ? modifier_flag_manager::operation::increase : modifier_flag_manager::operation::decrease;

    switch (usage_page) {
    case kHIDPage_KeyboardOrKeypad: {
      auto key = modifier_flag_manager::physical_keys::end_;

      switch (usage) {
      case kHIDUsage_KeyboardLeftControl:
        key = modifier_flag_manager::physical_keys::left_control;
        break;
      case kHIDUsage_KeyboardLeftShift:
        key = modifier_flag_manager::physical_keys::left_shift;
        break;
      case kHIDUsage_KeyboardLeftAlt:
        key = modifier_flag_manager::physical_keys::left_option;
        break;
      case kHIDUsage_KeyboardLeftGUI:
        key = modifier_flag_manager::physical_keys::left_command;
        break;
      case kHIDUsage_KeyboardRightControl:
        key = modifier_flag_manager::physical_keys::right_control;
        break;
      case kHIDUsage_KeyboardRightShift:
        key = modifier_flag_manager::physical_keys::right_shift;
        break;
      case kHIDUsage_KeyboardRightAlt:
        key = modifier_flag_manager::physical_keys::right_option;
        break;
      case kHIDUsage_KeyboardRightGUI:
        key = modifier_flag_manager::physical_keys::right_command;
        break;
      }

      if (key != modifier_flag_manager::physical_keys::end_) {
        modifier_flag_manager_.manipulate(key, operation);
        send_keyboard_input_report();
        return true;
      }
      break;
    }

    case kHIDPage_AppleVendorTopCase:
      if (usage == kHIDUsage_AV_TopCase_KeyboardFn) {
        modifier_flag_manager_.manipulate(modifier_flag_manager::physical_keys::fn, operation);
        console_user_client_.post_modifier_flags();
        return true;
      }
      break;
    }

    return false;
  }

  bool handle_function_key_event(uint32_t usage_page, uint32_t usage, bool pressed) {
    if (usage_page != kHIDPage_KeyboardOrKeypad) {
      return false;
    }

    auto event_type = pressed ? KRBN_EVENT_TYPE_KEY_DOWN : KRBN_EVENT_TYPE_KEY_UP;

#define POST_KEY(KEY)                                               \
  case kHIDUsage_Keyboard##KEY:                                     \
    console_user_client_.post_key(KRBN_KEY_CODE_##KEY, event_type); \
    return true;

    switch (usage) {
      POST_KEY(F1);
      POST_KEY(F2);
      POST_KEY(F3);
      POST_KEY(F4);
      POST_KEY(F5);
      POST_KEY(F6);
      POST_KEY(F7);
      POST_KEY(F8);
      POST_KEY(F9);
      POST_KEY(F10);
      POST_KEY(F11);
      POST_KEY(F12);
    }

    return false;
  }

  void handle_keyboard_event(uint32_t usage_page, uint32_t usage, bool pressed) {
    // ----------------------------------------
    // modify usage
    if (usage == kHIDUsage_KeyboardCapsLock) {
      usage = kHIDUsage_KeyboardDeleteOrBackspace;
    }

    if (modifier_flag_manager_.pressed(modifier_flag_manager::physical_keys::fn)) {
      switch (usage) {
      case kHIDUsage_KeyboardReturnOrEnter:
        usage = kHIDUsage_KeypadEnter;
        break;
      case kHIDUsage_KeyboardDeleteOrBackspace:
        usage = kHIDUsage_KeyboardDeleteForward;
        break;
      case kHIDUsage_KeyboardRightArrow:
        usage = kHIDUsage_KeyboardEnd;
        break;
      case kHIDUsage_KeyboardLeftArrow:
        usage = kHIDUsage_KeyboardHome;
        break;
      case kHIDUsage_KeyboardDownArrow:
        usage = kHIDUsage_KeyboardPageDown;
        break;
      case kHIDUsage_KeyboardUpArrow:
        usage = kHIDUsage_KeyboardPageUp;
        break;
      }
    }

    // ----------------------------------------
    if (handle_modifier_flag_event(usage_page, usage, pressed)) {
      console_user_client_.stop_key_repeat();
      return;
    }
    if (handle_function_key_event(usage_page, usage, pressed)) {
      return;
    }

    if (pressed) {
      pressed_key_usages_.push_back(usage);
      console_user_client_.stop_key_repeat();
    } else {
      pressed_key_usages_.remove(usage);
    }

    send_keyboard_input_report();
  }

  void send_keyboard_input_report(void) {
    // make report
    hid_report::keyboard_input report;
    report.modifiers = modifier_flag_manager_.get_hid_report_bits();

    while (pressed_key_usages_.size() > sizeof(report.keys)) {
      pressed_key_usages_.pop_front();
    }

    int i = 0;
    for (const auto& u : pressed_key_usages_) {
      report.keys[i] = u;
      ++i;
    }

    // send new report only if it is changed from last report.
    if (last_keyboard_input_report_ != report) {
      last_keyboard_input_report_ = report;

      auto kr = iokit_user_client_.call_struct_method(static_cast<uint32_t>(virtual_hid_manager_user_client_method::keyboard_input_report),
                                                      static_cast<const void*>(&report), sizeof(report),
                                                      nullptr, 0);
      if (kr != KERN_SUCCESS) {
        logger::get_logger().error("failed to sent report: 0x{0:x}", kr);
      }
    }
  }

  iokit_user_client iokit_user_client_;
  IOHIDManagerRef _Nullable manager_;
  std::unordered_map<IOHIDDeviceRef, std::unique_ptr<human_interface_device>> hids_;
  std::list<uint32_t> pressed_key_usages_;

  modifier_flag_manager modifier_flag_manager_;
  hid_report::keyboard_input last_keyboard_input_report_;

  console_user_client console_user_client_;
};
<commit_msg>add fn_changed_keys_<commit_after>#pragma once

#include "apple_hid_usage_tables.hpp"
#include "console_user_client.hpp"
#include "constants.hpp"
#include "hid_report.hpp"
#include "human_interface_device.hpp"
#include "iokit_user_client.hpp"
#include "iokit_utility.hpp"
#include "logger.hpp"
#include "modifier_flag_manager.hpp"
#include "userspace_defs.h"
#include "virtual_hid_manager_user_client_method.hpp"

class event_grabber final {
public:
  event_grabber(void) : iokit_user_client_(logger::get_logger(), "org_pqrs_driver_VirtualHIDManager", kIOHIDServerConnectType),
                        console_user_client_(modifier_flag_manager_) {
    manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
    if (!manager_) {
      return;
    }

    auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries({
        std::make_pair(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard),
        // std::make_pair(kHIDPage_Consumer, kHIDUsage_Csmr_ConsumerControl),
        // std::make_pair(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse),
    });
    if (device_matching_dictionaries) {
      IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
      CFRelease(device_matching_dictionaries);

      IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
      IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

      IOHIDManagerScheduleWithRunLoop(manager_, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    }
  }

  ~event_grabber(void) {
    if (manager_) {
      IOHIDManagerUnscheduleFromRunLoop(manager_, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
      CFRelease(manager_);
      manager_ = nullptr;
    }
  }

private:
  static void static_device_matching_callback(void* _Nullable context, IOReturn result, void* _Nullable sender, IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<event_grabber*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    hids_[device] = std::make_unique<human_interface_device>(device);
    auto& dev = hids_[device];

    std::cout << "matching: " << std::endl
              << "  vendor_id:0x" << std::hex << dev->get_vendor_id() << std::endl
              << "  product_id:0x" << std::hex << dev->get_product_id() << std::endl
              << "  location_id:0x" << std::hex << dev->get_location_id() << std::endl
              << "  serial_number:" << dev->get_serial_number_string() << std::endl
              << "  manufacturer:" << dev->get_manufacturer() << std::endl
              << "  product:" << dev->get_product() << std::endl
              << "  transport:" << dev->get_transport() << std::endl;

    if (dev->get_serial_number_string() == "org.pqrs.driver.VirtualHIDKeyboard") {
      return;
    }

    if (dev->get_manufacturer() != "pqrs.org") {
      if (dev->get_manufacturer() == "Apple Inc.") {
        dev->grab(boost::bind(&event_grabber::value_callback, this, _1, _2, _3, _4, _5));
      }
    }
  }

  static void static_device_removal_callback(void* _Nullable context, IOReturn result, void* _Nullable sender, IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<event_grabber*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    auto it = hids_.find(device);
    if (it != hids_.end()) {
      auto& dev = it->second;
      if (dev) {
        std::cout << "removal vendor_id:0x" << std::hex << dev->get_vendor_id() << " product_id:0x" << std::hex << dev->get_product_id() << std::endl;
        hids_.erase(it);
      }
    }
  }

  void value_callback(IOHIDValueRef _Nonnull value,
                      IOHIDElementRef _Nonnull element,
                      uint32_t usage_page,
                      uint32_t usage,
                      CFIndex integer_value) {

#if 0
    std::cout << "element" << std::endl
              << "  usage_page:0x" << std::hex << usage_page << std::endl
              << "  usage:0x" << std::hex << usage << std::endl
              << "  type:" << IOHIDElementGetType(element) << std::endl
              << "  length:" << IOHIDValueGetLength(value) << std::endl
              << "  integer_value:" << integer_value << std::endl;
#endif

    switch (usage_page) {
    case kHIDPage_KeyboardOrKeypad: {
      bool pressed = integer_value;
      handle_keyboard_event(usage_page, usage, pressed);
      break;
    }

    case kHIDPage_AppleVendorTopCase:
      if (usage == kHIDUsage_AV_TopCase_KeyboardFn) {
        bool pressed = integer_value;
        handle_keyboard_event(usage_page, usage, pressed);
      }
      break;

    default:
      break;
    }
  }

  bool handle_modifier_flag_event(uint32_t usage_page, uint32_t usage, bool pressed) {
    auto operation = pressed ? modifier_flag_manager::operation::increase : modifier_flag_manager::operation::decrease;

    switch (usage_page) {
    case kHIDPage_KeyboardOrKeypad: {
      auto key = modifier_flag_manager::physical_keys::end_;

      switch (usage) {
      case kHIDUsage_KeyboardLeftControl:
        key = modifier_flag_manager::physical_keys::left_control;
        break;
      case kHIDUsage_KeyboardLeftShift:
        key = modifier_flag_manager::physical_keys::left_shift;
        break;
      case kHIDUsage_KeyboardLeftAlt:
        key = modifier_flag_manager::physical_keys::left_option;
        break;
      case kHIDUsage_KeyboardLeftGUI:
        key = modifier_flag_manager::physical_keys::left_command;
        break;
      case kHIDUsage_KeyboardRightControl:
        key = modifier_flag_manager::physical_keys::right_control;
        break;
      case kHIDUsage_KeyboardRightShift:
        key = modifier_flag_manager::physical_keys::right_shift;
        break;
      case kHIDUsage_KeyboardRightAlt:
        key = modifier_flag_manager::physical_keys::right_option;
        break;
      case kHIDUsage_KeyboardRightGUI:
        key = modifier_flag_manager::physical_keys::right_command;
        break;
      }

      if (key != modifier_flag_manager::physical_keys::end_) {
        modifier_flag_manager_.manipulate(key, operation);
        send_keyboard_input_report();
        return true;
      }
      break;
    }

    case kHIDPage_AppleVendorTopCase:
      if (usage == kHIDUsage_AV_TopCase_KeyboardFn) {
        modifier_flag_manager_.manipulate(modifier_flag_manager::physical_keys::fn, operation);
        console_user_client_.post_modifier_flags();
        return true;
      }
      break;
    }

    return false;
  }

  bool handle_function_key_event(uint32_t usage_page, uint32_t usage, bool pressed) {
    if (usage_page != kHIDPage_KeyboardOrKeypad) {
      return false;
    }

    auto event_type = pressed ? KRBN_EVENT_TYPE_KEY_DOWN : KRBN_EVENT_TYPE_KEY_UP;

#define POST_KEY(KEY)                                               \
  case kHIDUsage_Keyboard##KEY:                                     \
    console_user_client_.post_key(KRBN_KEY_CODE_##KEY, event_type); \
    return true;

    switch (usage) {
      POST_KEY(F1);
      POST_KEY(F2);
      POST_KEY(F3);
      POST_KEY(F4);
      POST_KEY(F5);
      POST_KEY(F6);
      POST_KEY(F7);
      POST_KEY(F8);
      POST_KEY(F9);
      POST_KEY(F10);
      POST_KEY(F11);
      POST_KEY(F12);
    }

    return false;
  }

  void handle_keyboard_event(uint32_t usage_page, uint32_t usage, bool pressed) {
    // ----------------------------------------
    // modify usage
    if (usage == kHIDUsage_KeyboardCapsLock) {
      usage = kHIDUsage_KeyboardDeleteOrBackspace;
    }

    // change fn+arrow keys, etc.
    // TODO: integrate into __KeyToKey__.
    if ((pressed && modifier_flag_manager_.pressed(modifier_flag_manager::physical_keys::fn)) ||
        std::find(fn_changed_keys_.begin(), fn_changed_keys_.end(), usage) != fn_changed_keys_.end()) {
      bool changed = false;
      uint32_t new_usage = 0;
      switch (usage) {
        case kHIDUsage_KeyboardReturnOrEnter:
        new_usage = kHIDUsage_KeypadEnter;
        changed = true;
        break;
      case kHIDUsage_KeyboardDeleteOrBackspace:
        new_usage = kHIDUsage_KeyboardDeleteForward;
        changed = true;
        break;
      case kHIDUsage_KeyboardRightArrow:
        new_usage = kHIDUsage_KeyboardEnd;
        changed = true;
        break;
      case kHIDUsage_KeyboardLeftArrow:
        new_usage = kHIDUsage_KeyboardHome;
        changed = true;
        break;
      case kHIDUsage_KeyboardDownArrow:
        new_usage = kHIDUsage_KeyboardPageDown;
        changed = true;
        break;
      case kHIDUsage_KeyboardUpArrow:
        new_usage = kHIDUsage_KeyboardPageUp;
        changed = true;
        break;
      }

      if (changed) {
        if (pressed) {
          fn_changed_keys_.push_back(usage);
        } else {
          fn_changed_keys_.remove(usage);
        }
        usage = new_usage;
      }
    }

    // ----------------------------------------
    if (handle_modifier_flag_event(usage_page, usage, pressed)) {
      console_user_client_.stop_key_repeat();
      return;
    }
    if (handle_function_key_event(usage_page, usage, pressed)) {
      return;
    }

    if (pressed) {
      pressed_key_usages_.push_back(usage);
      console_user_client_.stop_key_repeat();
    } else {
      pressed_key_usages_.remove(usage);
    }

    send_keyboard_input_report();
  }

  void send_keyboard_input_report(void) {
    // make report
    hid_report::keyboard_input report;
    report.modifiers = modifier_flag_manager_.get_hid_report_bits();

    while (pressed_key_usages_.size() > sizeof(report.keys)) {
      pressed_key_usages_.pop_front();
    }

    int i = 0;
    for (const auto& u : pressed_key_usages_) {
      report.keys[i] = u;
      ++i;
    }

    // send new report only if it is changed from last report.
    if (last_keyboard_input_report_ != report) {
      last_keyboard_input_report_ = report;

      auto kr = iokit_user_client_.call_struct_method(static_cast<uint32_t>(virtual_hid_manager_user_client_method::keyboard_input_report),
                                                      static_cast<const void*>(&report), sizeof(report),
                                                      nullptr, 0);
      if (kr != KERN_SUCCESS) {
        logger::get_logger().error("failed to sent report: 0x{0:x}", kr);
      }
    }
  }

  iokit_user_client iokit_user_client_;
  IOHIDManagerRef _Nullable manager_;
  std::unordered_map<IOHIDDeviceRef, std::unique_ptr<human_interface_device>> hids_;
  std::list<uint32_t> pressed_key_usages_;
  std::list<uint32_t> fn_changed_keys_;

  modifier_flag_manager modifier_flag_manager_;
  hid_report::keyboard_input last_keyboard_input_report_;

  console_user_client console_user_client_;
};
<|endoftext|>


<commit_before>#include <errno.h>
#include <nan.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include "spidevice.h"
#include "transfer.h"
#include "util.h"

// TODO
// - Make sure it works when the message array is empty

static int Transfer(
  int fd,
  spi_ioc_transfer *spiTransfers,
  uint32_t transferCount
) {
  return ioctl(fd, SPI_IOC_MESSAGE(transferCount), spiTransfers);
}

class TransferWorker : public SpiAsyncWorker {
public:
  TransferWorker(
    Nan::Callback *callback,
    int fd,
    v8::Local<v8::Value> message,
    spi_ioc_transfer *spiTransfers,
    uint32_t transferCount
  ) : SpiAsyncWorker(callback),
    fd_(fd),
    spiTransfers_(spiTransfers),
    transferCount_(transferCount) {
    SaveToPersistent("message", message);
  }

  ~TransferWorker() {
  }

  void Execute() {
    int ret = Transfer(fd_, spiTransfers_, transferCount_);
    free(spiTransfers_);
    if (ret == -1) {
      SetErrorNo(errno);
      SetErrorSyscall("transfer");
    }
  }

  void HandleOKCallback() {
    Nan::HandleScope scope;

    v8::Local<v8::Value> message = GetFromPersistent("message");

    v8::Local<v8::Value> argv[] = {
      Nan::Null(),
      message
    };

    callback->Call(2, argv);
  }

private:
  int fd_;
  spi_ioc_transfer *spiTransfers_;
  uint32_t transferCount_;
};

static int32_t ToSpiTransfers(
  v8::Local<v8::Array> message,
  spi_ioc_transfer *spiTransfers
) {
  for (unsigned i = 0; i < message->Length(); ++i) {
    // Transfer

    v8::Local<v8::Value> transfer = message->Get(i);

    if (!transfer->IsObject()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL, "toSpiTransfers", "a transfer being should be an object"
        )
      );
      return -1;
    }

    v8::Local<v8::Object> msg = v8::Local<v8::Object>::Cast(transfer);

    // byteLength

    v8::Local<v8::Value> byteLength =
      Nan::Get(msg, Nan::New<v8::String>("byteLength").ToLocalChecked()).
      ToLocalChecked();

    if (byteLength->IsUndefined()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL, "toSpiTransfers", "transfer byteLength not specified"
        )
      );
      return -1;
    } else if (!byteLength->IsUint32()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer byteLength should be an unsigned integer"
        )
      );
      return -1;
    }

    uint32_t length = byteLength->Uint32Value();
    spiTransfers[i].len = length;

    // sendBuffer

    v8::Local<v8::Value> sendBuffer =
      Nan::Get(msg, Nan::New<v8::String>("sendBuffer").ToLocalChecked()).
      ToLocalChecked();

    if (sendBuffer->IsNull() || sendBuffer->IsUndefined()) {
      // No sendBuffer so tx_buf should be NULL. This is already the case.
    } else if (node::Buffer::HasInstance(sendBuffer)) {
      if (node::Buffer::Length(sendBuffer) < length) {
        Nan::ThrowError(
          Nan::ErrnoException(
            EINVAL,
            "toSpiTransfers",
            "transfer sendBuffer contains less than byteLength bytes"
          )
        );
        return -1;
      }
      spiTransfers[i].tx_buf = (__u64) node::Buffer::Data(sendBuffer);
    } else {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer sendBuffer should be null, undefined, or a Buffer object"
        )
      );
      return -1;
    }

    // receiveBuffer

    v8::Local<v8::Value> receiveBuffer =
      Nan::Get(msg, Nan::New<v8::String>("receiveBuffer").ToLocalChecked()).
      ToLocalChecked();

    if (receiveBuffer->IsNull() || receiveBuffer->IsUndefined()) {
      // No receiveBuffer so rx_buf should be NULL. This is already the case.
    } else if (node::Buffer::HasInstance(receiveBuffer)) {
      if (node::Buffer::Length(receiveBuffer) < length) {
        Nan::ThrowError(
          Nan::ErrnoException(
            EINVAL,
            "toSpiTransfers",
            "transfer receiveBuffer contains less than byteLength bytes"
          )
        );
        return -1;
      }
      spiTransfers[i].rx_buf = (__u64) node::Buffer::Data(receiveBuffer);
    } else {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer receiveBuffer should be null, undefined, or a Buffer object"
        )
      );
      return -1;
    }

    // sendBuffer and receiveBuffer

    if ((sendBuffer->IsNull() || sendBuffer->IsUndefined()) &&
        (receiveBuffer->IsNull() || receiveBuffer->IsUndefined())) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer contains neither a sendBuffer nor a receiveBuffer"
        )
      );
      return -1;
    }

    // speed

    v8::Local<v8::Value> speed = Nan::Get(msg,
      Nan::New<v8::String>("speed").ToLocalChecked()).ToLocalChecked();

    if (speed->IsUndefined()) {
      // No speed defined, nothing to do.
    } else if (!speed->IsUint32()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer speed should be an unsigned integer"
        )
      );
      return -1;
    } else {
      spiTransfers[i].speed_hz = speed->Uint32Value();
    }

    // chipSelectChange

    v8::Local<v8::Value> chipSelectChange =
      Nan::Get(msg, Nan::New<v8::String>("chipSelectChange").ToLocalChecked()).
      ToLocalChecked();

    if (chipSelectChange->IsUndefined()) {
      // No chipSelectChange defined, nothing to do.
    } else if (!chipSelectChange->IsBoolean()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer chipSelectChange should be a boolean"
        )
      );
      return -1;
    } else {
      spiTransfers[i].cs_change = chipSelectChange->BooleanValue() ? 1 : 0;
    }
  }

  return 0;
}

void Transfer(Nan::NAN_METHOD_ARGS_TYPE info) {
  SpiDevice *device = Nan::ObjectWrap::Unwrap<SpiDevice>(info.This());
  int fd = device->Fd();

  if (fd == -1) {
    return Nan::ThrowError(
      Nan::ErrnoException(
        EPERM, "transfer", "device closed, operation not permitted"
      )
    );
  }

  if (info.Length() < 2 ||
      !info[0]->IsArray() ||
      !info[1]->IsFunction()) {
    return Nan::ThrowError(
      Nan::ErrnoException(
        EINVAL,
        "transfer",
        "incorrect arguments passed to transfer(message, cb)"
      )
    );
  }

  v8::Local<v8::Array> message = info[0].As<v8::Array>();
  Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());

  spi_ioc_transfer *spiTransfers = (spi_ioc_transfer *)
    malloc(message->Length() * sizeof(spi_ioc_transfer));
  memset(spiTransfers, 0, message->Length() * sizeof(spi_ioc_transfer));

  if (ToSpiTransfers(message, spiTransfers) == -1) {
    free(spiTransfers);
    return;
  }

  Nan::AsyncQueueWorker(new TransferWorker(
    callback,
    fd,
    message,
    spiTransfers,
    message->Length()
  ));

  info.GetReturnValue().Set(info.This());
}

void TransferSync(Nan::NAN_METHOD_ARGS_TYPE info) {
  SpiDevice *device = Nan::ObjectWrap::Unwrap<SpiDevice>(info.This());
  int fd = device->Fd();

  if (fd == -1) {
    return Nan::ThrowError(
      Nan::ErrnoException(
        EPERM, "transferSync", "device closed, operation not permitted"
      )
    );
  }

  if (info.Length() < 1 || !info[0]->IsArray()) {
    return Nan::ThrowError(
      Nan::ErrnoException(
        EINVAL,
        "transfer",
        "incorrect arguments passed to transferSync(message)"
      )
    );
  }

  v8::Local<v8::Array> message = info[0].As<v8::Array>();

  spi_ioc_transfer *spiTransfers = (spi_ioc_transfer *)
    malloc(message->Length() * sizeof(spi_ioc_transfer));
  memset(spiTransfers, 0, message->Length() * sizeof(spi_ioc_transfer));

  if (ToSpiTransfers(message, spiTransfers) == 0) {
    if (Transfer(fd, spiTransfers, message->Length()) == -1) {
      Nan::ThrowError(Nan::ErrnoException(errno, "transferSync", ""));
    }
  }

  free(spiTransfers);

  info.GetReturnValue().Set(info.This());
}

<commit_msg>make message readable<commit_after>#include <errno.h>
#include <nan.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include "spidevice.h"
#include "transfer.h"
#include "util.h"

// TODO
// - Make sure it works when the message array is empty

static int Transfer(
  int fd,
  spi_ioc_transfer *spiTransfers,
  uint32_t transferCount
) {
  return ioctl(fd, SPI_IOC_MESSAGE(transferCount), spiTransfers);
}

class TransferWorker : public SpiAsyncWorker {
public:
  TransferWorker(
    Nan::Callback *callback,
    int fd,
    v8::Local<v8::Value> message,
    spi_ioc_transfer *spiTransfers,
    uint32_t transferCount
  ) : SpiAsyncWorker(callback),
    fd_(fd),
    spiTransfers_(spiTransfers),
    transferCount_(transferCount) {
    SaveToPersistent("message", message);
  }

  ~TransferWorker() {
  }

  void Execute() {
    int ret = Transfer(fd_, spiTransfers_, transferCount_);
    free(spiTransfers_);
    if (ret == -1) {
      SetErrorNo(errno);
      SetErrorSyscall("transfer");
    }
  }

  void HandleOKCallback() {
    Nan::HandleScope scope;

    v8::Local<v8::Value> message = GetFromPersistent("message");

    v8::Local<v8::Value> argv[] = {
      Nan::Null(),
      message
    };

    callback->Call(2, argv);
  }

private:
  int fd_;
  spi_ioc_transfer *spiTransfers_;
  uint32_t transferCount_;
};

static int32_t ToSpiTransfers(
  v8::Local<v8::Array> message,
  spi_ioc_transfer *spiTransfers
) {
  for (unsigned i = 0; i < message->Length(); ++i) {
    // Transfer

    v8::Local<v8::Value> transfer = message->Get(i);

    if (!transfer->IsObject()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL, "toSpiTransfers", "a transfer should be an object"
        )
      );
      return -1;
    }

    v8::Local<v8::Object> msg = v8::Local<v8::Object>::Cast(transfer);

    // byteLength

    v8::Local<v8::Value> byteLength =
      Nan::Get(msg, Nan::New<v8::String>("byteLength").ToLocalChecked()).
      ToLocalChecked();

    if (byteLength->IsUndefined()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL, "toSpiTransfers", "transfer byteLength not specified"
        )
      );
      return -1;
    } else if (!byteLength->IsUint32()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer byteLength should be an unsigned integer"
        )
      );
      return -1;
    }

    uint32_t length = byteLength->Uint32Value();
    spiTransfers[i].len = length;

    // sendBuffer

    v8::Local<v8::Value> sendBuffer =
      Nan::Get(msg, Nan::New<v8::String>("sendBuffer").ToLocalChecked()).
      ToLocalChecked();

    if (sendBuffer->IsNull() || sendBuffer->IsUndefined()) {
      // No sendBuffer so tx_buf should be NULL. This is already the case.
    } else if (node::Buffer::HasInstance(sendBuffer)) {
      if (node::Buffer::Length(sendBuffer) < length) {
        Nan::ThrowError(
          Nan::ErrnoException(
            EINVAL,
            "toSpiTransfers",
            "transfer sendBuffer contains less than byteLength bytes"
          )
        );
        return -1;
      }
      spiTransfers[i].tx_buf = (__u64) node::Buffer::Data(sendBuffer);
    } else {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer sendBuffer should be null, undefined, or a Buffer object"
        )
      );
      return -1;
    }

    // receiveBuffer

    v8::Local<v8::Value> receiveBuffer =
      Nan::Get(msg, Nan::New<v8::String>("receiveBuffer").ToLocalChecked()).
      ToLocalChecked();

    if (receiveBuffer->IsNull() || receiveBuffer->IsUndefined()) {
      // No receiveBuffer so rx_buf should be NULL. This is already the case.
    } else if (node::Buffer::HasInstance(receiveBuffer)) {
      if (node::Buffer::Length(receiveBuffer) < length) {
        Nan::ThrowError(
          Nan::ErrnoException(
            EINVAL,
            "toSpiTransfers",
            "transfer receiveBuffer contains less than byteLength bytes"
          )
        );
        return -1;
      }
      spiTransfers[i].rx_buf = (__u64) node::Buffer::Data(receiveBuffer);
    } else {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer receiveBuffer should be null, undefined, or a Buffer object"
        )
      );
      return -1;
    }

    // sendBuffer and receiveBuffer

    if ((sendBuffer->IsNull() || sendBuffer->IsUndefined()) &&
        (receiveBuffer->IsNull() || receiveBuffer->IsUndefined())) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer contains neither a sendBuffer nor a receiveBuffer"
        )
      );
      return -1;
    }

    // speed

    v8::Local<v8::Value> speed = Nan::Get(msg,
      Nan::New<v8::String>("speed").ToLocalChecked()).ToLocalChecked();

    if (speed->IsUndefined()) {
      // No speed defined, nothing to do.
    } else if (!speed->IsUint32()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer speed should be an unsigned integer"
        )
      );
      return -1;
    } else {
      spiTransfers[i].speed_hz = speed->Uint32Value();
    }

    // chipSelectChange

    v8::Local<v8::Value> chipSelectChange =
      Nan::Get(msg, Nan::New<v8::String>("chipSelectChange").ToLocalChecked()).
      ToLocalChecked();

    if (chipSelectChange->IsUndefined()) {
      // No chipSelectChange defined, nothing to do.
    } else if (!chipSelectChange->IsBoolean()) {
      Nan::ThrowError(
        Nan::ErrnoException(
          EINVAL,
          "toSpiTransfers",
          "transfer chipSelectChange should be a boolean"
        )
      );
      return -1;
    } else {
      spiTransfers[i].cs_change = chipSelectChange->BooleanValue() ? 1 : 0;
    }
  }

  return 0;
}

void Transfer(Nan::NAN_METHOD_ARGS_TYPE info) {
  SpiDevice *device = Nan::ObjectWrap::Unwrap<SpiDevice>(info.This());
  int fd = device->Fd();

  if (fd == -1) {
    return Nan::ThrowError(
      Nan::ErrnoException(
        EPERM, "transfer", "device closed, operation not permitted"
      )
    );
  }

  if (info.Length() < 2 ||
      !info[0]->IsArray() ||
      !info[1]->IsFunction()) {
    return Nan::ThrowError(
      Nan::ErrnoException(
        EINVAL,
        "transfer",
        "incorrect arguments passed to transfer(message, cb)"
      )
    );
  }

  v8::Local<v8::Array> message = info[0].As<v8::Array>();
  Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());

  spi_ioc_transfer *spiTransfers = (spi_ioc_transfer *)
    malloc(message->Length() * sizeof(spi_ioc_transfer));
  memset(spiTransfers, 0, message->Length() * sizeof(spi_ioc_transfer));

  if (ToSpiTransfers(message, spiTransfers) == -1) {
    free(spiTransfers);
    return;
  }

  Nan::AsyncQueueWorker(new TransferWorker(
    callback,
    fd,
    message,
    spiTransfers,
    message->Length()
  ));

  info.GetReturnValue().Set(info.This());
}

void TransferSync(Nan::NAN_METHOD_ARGS_TYPE info) {
  SpiDevice *device = Nan::ObjectWrap::Unwrap<SpiDevice>(info.This());
  int fd = device->Fd();

  if (fd == -1) {
    return Nan::ThrowError(
      Nan::ErrnoException(
        EPERM, "transferSync", "device closed, operation not permitted"
      )
    );
  }

  if (info.Length() < 1 || !info[0]->IsArray()) {
    return Nan::ThrowError(
      Nan::ErrnoException(
        EINVAL,
        "transfer",
        "incorrect arguments passed to transferSync(message)"
      )
    );
  }

  v8::Local<v8::Array> message = info[0].As<v8::Array>();

  spi_ioc_transfer *spiTransfers = (spi_ioc_transfer *)
    malloc(message->Length() * sizeof(spi_ioc_transfer));
  memset(spiTransfers, 0, message->Length() * sizeof(spi_ioc_transfer));

  if (ToSpiTransfers(message, spiTransfers) == 0) {
    if (Transfer(fd, spiTransfers, message->Length()) == -1) {
      Nan::ThrowError(Nan::ErrnoException(errno, "transferSync", ""));
    }
  }

  free(spiTransfers);

  info.GetReturnValue().Set(info.This());
}

<|endoftext|>


<commit_before>/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "outputwindow.h"

#include "actionmanager/actionmanager.h"
#include "coreconstants.h"
#include "icore.h"

#include <utils/outputformatter.h>
#include <utils/synchronousprocess.h>

#include <QAction>
#include <QScrollBar>

using namespace Utils;

namespace Core {

namespace Internal {

class OutputWindowPrivate
{
public:
    OutputWindowPrivate()
        : outputWindowContext(0)
        , formatter(0)
        , enforceNewline(false)
        , scrollToBottom(false)
        , linksActive(true)
        , mousePressed(false)
        , maxLineCount(100000)
    {
    }

    ~OutputWindowPrivate()
    {
        ICore::removeContextObject(outputWindowContext);
        delete outputWindowContext;
    }

    IContext *outputWindowContext;
    Utils::OutputFormatter *formatter;

    bool enforceNewline;
    bool scrollToBottom;
    bool linksActive;
    bool mousePressed;
    int maxLineCount;
};

} // namespace Internal

/*******************/

OutputWindow::OutputWindow(Context context, QWidget *parent)
    : QPlainTextEdit(parent)
    , d(new Internal::OutputWindowPrivate)
{
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    //setCenterOnScroll(false);
    setFrameShape(QFrame::NoFrame);
    setMouseTracking(true);
    setUndoRedoEnabled(false);

    d->outputWindowContext = new IContext;
    d->outputWindowContext->setContext(context);
    d->outputWindowContext->setWidget(this);
    ICore::addContextObject(d->outputWindowContext);

    QAction *undoAction = new QAction(this);
    QAction *redoAction = new QAction(this);
    QAction *cutAction = new QAction(this);
    QAction *copyAction = new QAction(this);
    QAction *pasteAction = new QAction(this);
    QAction *selectAllAction = new QAction(this);

    ActionManager::registerAction(undoAction, Constants::UNDO, context);
    ActionManager::registerAction(redoAction, Constants::REDO, context);
    ActionManager::registerAction(cutAction, Constants::CUT, context);
    ActionManager::registerAction(copyAction, Constants::COPY, context);
    ActionManager::registerAction(pasteAction, Constants::PASTE, context);
    ActionManager::registerAction(selectAllAction, Constants::SELECTALL, context);

    connect(undoAction, SIGNAL(triggered()), this, SLOT(undo()));
    connect(redoAction, SIGNAL(triggered()), this, SLOT(redo()));
    connect(cutAction, SIGNAL(triggered()), this, SLOT(cut()));
    connect(copyAction, SIGNAL(triggered()), this, SLOT(copy()));
    connect(pasteAction, SIGNAL(triggered()), this, SLOT(paste()));
    connect(selectAllAction, SIGNAL(triggered()), this, SLOT(selectAll()));

    connect(this, SIGNAL(undoAvailable(bool)), undoAction, SLOT(setEnabled(bool)));
    connect(this, SIGNAL(redoAvailable(bool)), redoAction, SLOT(setEnabled(bool)));
    connect(this, SIGNAL(copyAvailable(bool)), cutAction, SLOT(setEnabled(bool)));  // OutputWindow never read-only
    connect(this, SIGNAL(copyAvailable(bool)), copyAction, SLOT(setEnabled(bool)));

    undoAction->setEnabled(false);
    redoAction->setEnabled(false);
    cutAction->setEnabled(false);
    copyAction->setEnabled(false);
}

OutputWindow::~OutputWindow()
{
    delete d;
}

void OutputWindow::mousePressEvent(QMouseEvent * e)
{
    d->mousePressed = true;
    QPlainTextEdit::mousePressEvent(e);
}

void OutputWindow::mouseReleaseEvent(QMouseEvent *e)
{
    d->mousePressed = false;

    if (d->linksActive) {
        const QString href = anchorAt(e->pos());
        if (d->formatter)
            d->formatter->handleLink(href);
    }

    // Mouse was released, activate links again
    d->linksActive = true;

    QPlainTextEdit::mouseReleaseEvent(e);
}

void OutputWindow::mouseMoveEvent(QMouseEvent *e)
{
    // Cursor was dragged to make a selection, deactivate links
    if (d->mousePressed && textCursor().hasSelection())
        d->linksActive = false;

    if (!d->linksActive || anchorAt(e->pos()).isEmpty())
        viewport()->setCursor(Qt::IBeamCursor);
    else
        viewport()->setCursor(Qt::PointingHandCursor);
    QPlainTextEdit::mouseMoveEvent(e);
}

void OutputWindow::resizeEvent(QResizeEvent *e)
{
    //Keep scrollbar at bottom of window while resizing, to ensure we keep scrolling
    //This can happen if window is resized while building, or if the horizontal scrollbar appears
    bool atBottom = isScrollbarAtBottom();
    QPlainTextEdit::resizeEvent(e);
    if (atBottom)
        scrollToBottom();
}

void OutputWindow::keyPressEvent(QKeyEvent *ev)
{
    QPlainTextEdit::keyPressEvent(ev);

    //Ensure we scroll also on Ctrl+Home or Ctrl+End
    if (ev->matches(QKeySequence::MoveToStartOfDocument))
        verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMinimum);
    else if (ev->matches(QKeySequence::MoveToEndOfDocument))
        verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
}

OutputFormatter *OutputWindow::formatter() const
{
    return d->formatter;
}

void OutputWindow::setFormatter(OutputFormatter *formatter)
{
    d->formatter = formatter;
    d->formatter->setPlainTextEdit(this);
}

void OutputWindow::showEvent(QShowEvent *e)
{
    QPlainTextEdit::showEvent(e);
    if (d->scrollToBottom)
        verticalScrollBar()->setValue(verticalScrollBar()->maximum());
    d->scrollToBottom = false;
}

QString OutputWindow::doNewlineEnforcement(const QString &out)
{
    d->scrollToBottom = true;
    QString s = out;
    if (d->enforceNewline) {
        s.prepend(QLatin1Char('\n'));
        d->enforceNewline = false;
    }

    if (s.endsWith(QLatin1Char('\n'))) {
        d->enforceNewline = true; // make appendOutputInline put in a newline next time
        s.chop(1);
    }

    return s;
}

void OutputWindow::setMaxLineCount(int count)
{
    d->maxLineCount = count;
    setMaximumBlockCount(d->maxLineCount);
}

int OutputWindow::maxLineCount() const
{
    return d->maxLineCount;
}

void OutputWindow::appendMessage(const QString &output, OutputFormat format)
{
    const QString out = SynchronousProcess::normalizeNewlines(output);
    setMaximumBlockCount(d->maxLineCount);
    const bool atBottom = isScrollbarAtBottom();

    if (format == ErrorMessageFormat || format == NormalMessageFormat) {

        d->formatter->appendMessage(doNewlineEnforcement(out), format);

    } else {

        bool sameLine = format == StdOutFormatSameLine
                     || format == StdErrFormatSameLine;

        if (sameLine) {
            d->scrollToBottom = true;

            int newline = -1;
            bool enforceNewline = d->enforceNewline;
            d->enforceNewline = false;

            if (!enforceNewline) {
                newline = out.indexOf(QLatin1Char('\n'));
                moveCursor(QTextCursor::End);
                if (newline != -1)
                    d->formatter->appendMessage(out.left(newline), format);// doesn't enforce new paragraph like appendPlainText
            }

            QString s = out.mid(newline+1);
            if (s.isEmpty()) {
                d->enforceNewline = true;
            } else {
                if (s.endsWith(QLatin1Char('\n'))) {
                    d->enforceNewline = true;
                    s.chop(1);
                }
                d->formatter->appendMessage(QLatin1Char('\n') + s, format);
            }
        } else {
            d->formatter->appendMessage(doNewlineEnforcement(out), format);
        }
    }

    if (atBottom)
        scrollToBottom();
    enableUndoRedo();
}

// TODO rename
void OutputWindow::appendText(const QString &textIn, const QTextCharFormat &format)
{
    const QString text = SynchronousProcess::normalizeNewlines(textIn);
    if (d->maxLineCount > 0 && document()->blockCount() >= d->maxLineCount)
        return;
    const bool atBottom = isScrollbarAtBottom();
    QTextCursor cursor = QTextCursor(document());
    cursor.movePosition(QTextCursor::End);
    cursor.beginEditBlock();
    cursor.insertText(doNewlineEnforcement(text), format);

    if (d->maxLineCount > 0 && document()->blockCount() >= d->maxLineCount) {
        QTextCharFormat tmp;
        tmp.setFontWeight(QFont::Bold);
        cursor.insertText(doNewlineEnforcement(tr("Additional output omitted") + QLatin1Char('\n')), tmp);
    }

    cursor.endEditBlock();
    if (atBottom)
        scrollToBottom();
}

bool OutputWindow::isScrollbarAtBottom() const
{
    return verticalScrollBar()->value() == verticalScrollBar()->maximum();
}

void OutputWindow::clear()
{
    d->enforceNewline = false;
    QPlainTextEdit::clear();
}

void OutputWindow::scrollToBottom()
{
    verticalScrollBar()->setValue(verticalScrollBar()->maximum());
    // QPlainTextEdit destroys the first calls value in case of multiline
    // text, so make sure that the scroll bar actually gets the value set.
    // Is a noop if the first call succeeded.
    verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}

void OutputWindow::grayOutOldContent()
{
    QTextCursor cursor = textCursor();
    cursor.movePosition(QTextCursor::End);
    QTextCharFormat endFormat = cursor.charFormat();

    cursor.select(QTextCursor::Document);

    QTextCharFormat format;
    const QColor bkgColor = palette().base().color();
    const QColor fgdColor = palette().text().color();
    double bkgFactor = 0.50;
    double fgdFactor = 1.-bkgFactor;
    format.setForeground(QColor((bkgFactor * bkgColor.red() + fgdFactor * fgdColor.red()),
                             (bkgFactor * bkgColor.green() + fgdFactor * fgdColor.green()),
                             (bkgFactor * bkgColor.blue() + fgdFactor * fgdColor.blue()) ));
    cursor.mergeCharFormat(format);

    cursor.movePosition(QTextCursor::End);
    cursor.setCharFormat(endFormat);
    cursor.insertBlock(QTextBlockFormat());
}

void OutputWindow::enableUndoRedo()
{
    setMaximumBlockCount(0);
    setUndoRedoEnabled(true);
}

void OutputWindow::setWordWrapEnabled(bool wrap)
{
    if (wrap)
        setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    else
        setWordWrapMode(QTextOption::NoWrap);
}

} // namespace Core
<commit_msg>Core: Use a fixed cursor in OutputWindow<commit_after>/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "outputwindow.h"

#include "actionmanager/actionmanager.h"
#include "coreconstants.h"
#include "icore.h"

#include <utils/outputformatter.h>
#include <utils/synchronousprocess.h>

#include <QAction>
#include <QScrollBar>

using namespace Utils;

namespace Core {

namespace Internal {

class OutputWindowPrivate
{
public:
    OutputWindowPrivate(QTextDocument *document)
        : outputWindowContext(0)
        , formatter(0)
        , enforceNewline(false)
        , scrollToBottom(false)
        , linksActive(true)
        , mousePressed(false)
        , maxLineCount(100000)
        , cursor(document)
    {
    }

    ~OutputWindowPrivate()
    {
        ICore::removeContextObject(outputWindowContext);
        delete outputWindowContext;
    }

    IContext *outputWindowContext;
    Utils::OutputFormatter *formatter;

    bool enforceNewline;
    bool scrollToBottom;
    bool linksActive;
    bool mousePressed;
    int maxLineCount;
    QTextCursor cursor;
};

} // namespace Internal

/*******************/

OutputWindow::OutputWindow(Context context, QWidget *parent)
    : QPlainTextEdit(parent)
    , d(new Internal::OutputWindowPrivate(document()))
{
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    //setCenterOnScroll(false);
    setFrameShape(QFrame::NoFrame);
    setMouseTracking(true);
    setUndoRedoEnabled(false);

    d->outputWindowContext = new IContext;
    d->outputWindowContext->setContext(context);
    d->outputWindowContext->setWidget(this);
    ICore::addContextObject(d->outputWindowContext);

    QAction *undoAction = new QAction(this);
    QAction *redoAction = new QAction(this);
    QAction *cutAction = new QAction(this);
    QAction *copyAction = new QAction(this);
    QAction *pasteAction = new QAction(this);
    QAction *selectAllAction = new QAction(this);

    ActionManager::registerAction(undoAction, Constants::UNDO, context);
    ActionManager::registerAction(redoAction, Constants::REDO, context);
    ActionManager::registerAction(cutAction, Constants::CUT, context);
    ActionManager::registerAction(copyAction, Constants::COPY, context);
    ActionManager::registerAction(pasteAction, Constants::PASTE, context);
    ActionManager::registerAction(selectAllAction, Constants::SELECTALL, context);

    connect(undoAction, SIGNAL(triggered()), this, SLOT(undo()));
    connect(redoAction, SIGNAL(triggered()), this, SLOT(redo()));
    connect(cutAction, SIGNAL(triggered()), this, SLOT(cut()));
    connect(copyAction, SIGNAL(triggered()), this, SLOT(copy()));
    connect(pasteAction, SIGNAL(triggered()), this, SLOT(paste()));
    connect(selectAllAction, SIGNAL(triggered()), this, SLOT(selectAll()));

    connect(this, SIGNAL(undoAvailable(bool)), undoAction, SLOT(setEnabled(bool)));
    connect(this, SIGNAL(redoAvailable(bool)), redoAction, SLOT(setEnabled(bool)));
    connect(this, SIGNAL(copyAvailable(bool)), cutAction, SLOT(setEnabled(bool)));  // OutputWindow never read-only
    connect(this, SIGNAL(copyAvailable(bool)), copyAction, SLOT(setEnabled(bool)));

    undoAction->setEnabled(false);
    redoAction->setEnabled(false);
    cutAction->setEnabled(false);
    copyAction->setEnabled(false);
}

OutputWindow::~OutputWindow()
{
    delete d;
}

void OutputWindow::mousePressEvent(QMouseEvent * e)
{
    d->mousePressed = true;
    QPlainTextEdit::mousePressEvent(e);
}

void OutputWindow::mouseReleaseEvent(QMouseEvent *e)
{
    d->mousePressed = false;

    if (d->linksActive) {
        const QString href = anchorAt(e->pos());
        if (d->formatter)
            d->formatter->handleLink(href);
    }

    // Mouse was released, activate links again
    d->linksActive = true;

    QPlainTextEdit::mouseReleaseEvent(e);
}

void OutputWindow::mouseMoveEvent(QMouseEvent *e)
{
    // Cursor was dragged to make a selection, deactivate links
    if (d->mousePressed && textCursor().hasSelection())
        d->linksActive = false;

    if (!d->linksActive || anchorAt(e->pos()).isEmpty())
        viewport()->setCursor(Qt::IBeamCursor);
    else
        viewport()->setCursor(Qt::PointingHandCursor);
    QPlainTextEdit::mouseMoveEvent(e);
}

void OutputWindow::resizeEvent(QResizeEvent *e)
{
    //Keep scrollbar at bottom of window while resizing, to ensure we keep scrolling
    //This can happen if window is resized while building, or if the horizontal scrollbar appears
    bool atBottom = isScrollbarAtBottom();
    QPlainTextEdit::resizeEvent(e);
    if (atBottom)
        scrollToBottom();
}

void OutputWindow::keyPressEvent(QKeyEvent *ev)
{
    QPlainTextEdit::keyPressEvent(ev);

    //Ensure we scroll also on Ctrl+Home or Ctrl+End
    if (ev->matches(QKeySequence::MoveToStartOfDocument))
        verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMinimum);
    else if (ev->matches(QKeySequence::MoveToEndOfDocument))
        verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
}

OutputFormatter *OutputWindow::formatter() const
{
    return d->formatter;
}

void OutputWindow::setFormatter(OutputFormatter *formatter)
{
    d->formatter = formatter;
    d->formatter->setPlainTextEdit(this);
}

void OutputWindow::showEvent(QShowEvent *e)
{
    QPlainTextEdit::showEvent(e);
    if (d->scrollToBottom)
        verticalScrollBar()->setValue(verticalScrollBar()->maximum());
    d->scrollToBottom = false;
}

QString OutputWindow::doNewlineEnforcement(const QString &out)
{
    d->scrollToBottom = true;
    QString s = out;
    if (d->enforceNewline) {
        s.prepend(QLatin1Char('\n'));
        d->enforceNewline = false;
    }

    if (s.endsWith(QLatin1Char('\n'))) {
        d->enforceNewline = true; // make appendOutputInline put in a newline next time
        s.chop(1);
    }

    return s;
}

void OutputWindow::setMaxLineCount(int count)
{
    d->maxLineCount = count;
    setMaximumBlockCount(d->maxLineCount);
}

int OutputWindow::maxLineCount() const
{
    return d->maxLineCount;
}

void OutputWindow::appendMessage(const QString &output, OutputFormat format)
{
    const QString out = SynchronousProcess::normalizeNewlines(output);
    setMaximumBlockCount(d->maxLineCount);
    const bool atBottom = isScrollbarAtBottom();

    if (format == ErrorMessageFormat || format == NormalMessageFormat) {

        d->formatter->appendMessage(doNewlineEnforcement(out), format);

    } else {

        bool sameLine = format == StdOutFormatSameLine
                     || format == StdErrFormatSameLine;

        if (sameLine) {
            d->scrollToBottom = true;

            int newline = -1;
            bool enforceNewline = d->enforceNewline;
            d->enforceNewline = false;

            if (!enforceNewline) {
                newline = out.indexOf(QLatin1Char('\n'));
                moveCursor(QTextCursor::End);
                if (newline != -1)
                    d->formatter->appendMessage(out.left(newline), format);// doesn't enforce new paragraph like appendPlainText
            }

            QString s = out.mid(newline+1);
            if (s.isEmpty()) {
                d->enforceNewline = true;
            } else {
                if (s.endsWith(QLatin1Char('\n'))) {
                    d->enforceNewline = true;
                    s.chop(1);
                }
                d->formatter->appendMessage(QLatin1Char('\n') + s, format);
            }
        } else {
            d->formatter->appendMessage(doNewlineEnforcement(out), format);
        }
    }

    if (atBottom)
        scrollToBottom();
    enableUndoRedo();
}

// TODO rename
void OutputWindow::appendText(const QString &textIn, const QTextCharFormat &format)
{
    const QString text = SynchronousProcess::normalizeNewlines(textIn);
    if (d->maxLineCount > 0 && document()->blockCount() >= d->maxLineCount)
        return;
    const bool atBottom = isScrollbarAtBottom();
    if (!d->cursor.atEnd())
        d->cursor.movePosition(QTextCursor::End);
    d->cursor.beginEditBlock();
    d->cursor.insertText(doNewlineEnforcement(text), format);

    if (d->maxLineCount > 0 && document()->blockCount() >= d->maxLineCount) {
        QTextCharFormat tmp;
        tmp.setFontWeight(QFont::Bold);
        d->cursor.insertText(doNewlineEnforcement(tr("Additional output omitted") + QLatin1Char('\n')), tmp);
    }

    d->cursor.endEditBlock();
    if (atBottom)
        scrollToBottom();
}

bool OutputWindow::isScrollbarAtBottom() const
{
    return verticalScrollBar()->value() == verticalScrollBar()->maximum();
}

void OutputWindow::clear()
{
    d->enforceNewline = false;
    QPlainTextEdit::clear();
}

void OutputWindow::scrollToBottom()
{
    verticalScrollBar()->setValue(verticalScrollBar()->maximum());
    // QPlainTextEdit destroys the first calls value in case of multiline
    // text, so make sure that the scroll bar actually gets the value set.
    // Is a noop if the first call succeeded.
    verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}

void OutputWindow::grayOutOldContent()
{
    if (!d->cursor.atEnd())
        d->cursor.movePosition(QTextCursor::End);
    QTextCharFormat endFormat = d->cursor.charFormat();

    d->cursor.select(QTextCursor::Document);

    QTextCharFormat format;
    const QColor bkgColor = palette().base().color();
    const QColor fgdColor = palette().text().color();
    double bkgFactor = 0.50;
    double fgdFactor = 1.-bkgFactor;
    format.setForeground(QColor((bkgFactor * bkgColor.red() + fgdFactor * fgdColor.red()),
                             (bkgFactor * bkgColor.green() + fgdFactor * fgdColor.green()),
                             (bkgFactor * bkgColor.blue() + fgdFactor * fgdColor.blue()) ));
    d->cursor.mergeCharFormat(format);

    if (!d->cursor.atEnd())
        d->cursor.movePosition(QTextCursor::End);
    d->cursor.setCharFormat(endFormat);
    d->cursor.insertBlock(QTextBlockFormat());
}

void OutputWindow::enableUndoRedo()
{
    setMaximumBlockCount(0);
    setUndoRedoEnabled(true);
}

void OutputWindow::setWordWrapEnabled(bool wrap)
{
    if (wrap)
        setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    else
        setWordWrapMode(QTextOption::NoWrap);
}

} // namespace Core
<|endoftext|>