<commit_before>/*
 *      Copyright 2017 <NAME> <<EMAIL>>
 *
 *      This program 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.
 *
 *      This program 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.
 */

#include "ms735.h"
#include "qhiddevice.h"
#include "qhidmonitor.h"

#include <QRgb>
#include <QThread>

#define VENDOR  0x04D9
#define PRODUCT 0xA100
#define GENERIC_USAGE_PAGE 0xFF00
#define GENERIC_USAGE      0xFF00
#define EVENT_USAGE_PAGE   0xFF01
#define EVENT_USAGE        0x0001

#define PAGE_SIZE 128

Q_LOGGING_CATEGORY(UsbIo, "usb")

#ifndef qCInfo
// QT 5.2 does not have qCInfo
#define qCInfo qCWarning
#endif

static char crc(const QByteArray data)
{
    char sum = -1;

    foreach (auto ch, data)
    {
        sum -= ch;
    }

    return sum;
}

MS735::MS735(QObject *parent)
    : QObject(parent)
    , device(new QHIDDevice(VENDOR, PRODUCT, GENERIC_USAGE_PAGE, GENERIC_USAGE, this))
    , eventDevice(new QHIDDevice(VENDOR, PRODUCT, EVENT_USAGE_PAGE, EVENT_USAGE, this))
    , monitor(new QHIDMonitor(VENDOR, PRODUCT, this))
    , timerId(0)

{
    connect(monitor, SIGNAL(deviceArrival(QString)), this, SLOT(deviceArrival(QString)));
    connect(monitor, SIGNAL(deviceRemove()), this, SLOT(deviceRemove()));

    // On osx open may fail on first try with error 60.
    for (int retry = 0; retry < 5 && report(CmdEventMask, EventAll).isNull(); ++retry)
    {
        QThread::msleep(20);
        device->open(VENDOR, PRODUCT, GENERIC_USAGE_PAGE, GENERIC_USAGE);
        eventDevice->open(VENDOR, PRODUCT, EVENT_USAGE_PAGE, EVENT_USAGE);
    }

    timerId = startTimer(10);
}

MS735::~MS735()
{
    if (timerId)
    {
        killTimer(timerId);
        timerId = 0;
    }

    foreach (auto page, cache)
    {
        delete[] page.second;
    }
}

void MS735::deviceArrival(const QString &path)
{
    qCInfo(UsbIo) << "Detected device arrival at" << path;
    connectChanged(device->open(VENDOR, PRODUCT, GENERIC_USAGE_PAGE, GENERIC_USAGE));
    eventDevice->open(VENDOR, PRODUCT, EVENT_USAGE_PAGE, EVENT_USAGE);
}

void MS735::deviceRemove()
{
    qCInfo(UsbIo) << "Detected device removal";
    connectChanged(false);
}

QByteArray MS735::report(Command b1, char b2, char b3, char b4, char b5, char b6, char b7)
{
    QByteArray data;
    data.reserve(9);
    data.push_back('\x0');
    data.push_back((char)b1);
    data.push_back(b2);
    data.push_back(b3);
    data.push_back(b4);
    data.push_back(b5);
    data.push_back(b6);
    data.push_back(b7);
    data.push_back(crc(data));

    qCDebug(UsbIo) << "send" << data.toHex();
    int sent = device->sendFeatureReport(data.cbegin(), data.length());
    if (sent != data.length())
    {
        qCWarning(UsbIo) << "send failed: got" << sent << "expected" << data.length();
        return nullptr;
    }

    data.fill('\x0');
    int read = device->getFeatureReport(data.begin(), data.length());
    if (read != data.length())<commit_msg>Fixed osx bug in hidapi, so no need for retry. Stop event timer on device disconnect.<commit_after>/*
 *      Copyright 2017 <NAME> <<EMAIL>>
 *
 *      This program 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.
 *
 *      This program 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.
 */

#include "ms735.h"
#include "qhiddevice.h"
#include "qhidmonitor.h"

#include <QRgb>
#include <QThread>

#define VENDOR  0x04D9
#define PRODUCT 0xA100
#define GENERIC_USAGE_PAGE 0xFF00
#define GENERIC_USAGE      0xFF00
#define EVENT_USAGE_PAGE   0xFF01
#define EVENT_USAGE        0x0001

#define PAGE_SIZE 128

Q_LOGGING_CATEGORY(UsbIo, "usb")

#ifndef qCInfo
// QT 5.2 does not have qCInfo
#define qCInfo qCWarning
#endif

static char crc(const QByteArray data)
{
    char sum = -1;

    foreach (auto ch, data)
    {
        sum -= ch;
    }

    return sum;
}

MS735::MS735(QObject *parent)
    : QObject(parent)
    , device(new QHIDDevice(VENDOR, PRODUCT, GENERIC_USAGE_PAGE, GENERIC_USAGE, this))
    , eventDevice(new QHIDDevice(VENDOR, PRODUCT, EVENT_USAGE_PAGE, EVENT_USAGE, this))
    , monitor(new QHIDMonitor(VENDOR, PRODUCT, this))
    , timerId(0)

{
    connect(monitor, SIGNAL(deviceArrival(QString)), this, SLOT(deviceArrival(QString)));
    connect(monitor, SIGNAL(deviceRemove()), this, SLOT(deviceRemove()));

    if (eventDevice->isValid() && !report(CmdEventMask, EventAll).isNull())
    {
        timerId = startTimer(10);
    }
}

MS735::~MS735()
{
    if (timerId)
    {
        killTimer(timerId);
        timerId = 0;
    }

    foreach (auto page, cache)
    {
        delete[] page.second;
    }
}

void MS735::deviceArrival(const QString &path)
{
    qCInfo(UsbIo) << "Detected device arrival at" << path;
    auto connected = device->open(VENDOR, PRODUCT, GENERIC_USAGE_PAGE, GENERIC_USAGE) && ping();
    connectChanged(connected);
    if (connected)
    {
        eventDevice->open(VENDOR, PRODUCT, EVENT_USAGE_PAGE, EVENT_USAGE);

        if (eventDevice->isValid() && !report(CmdEventMask, EventAll).isNull())
        {
            timerId = startTimer(10);
        }
    }
}

void MS735::deviceRemove()
{
    qCInfo(UsbIo) << "Detected device removal";
    if (timerId)
    {
        killTimer(timerId);
        timerId = 0;
    }
    connectChanged(false);
}

QByteArray MS735::report(Command b1, char b2, char b3, char b4, char b5, char b6, char b7)
{
    QByteArray data;
    data.reserve(9);
    data.push_back('\x0');
    data.push_back((char)b1);
    data.push_back(b2);
    data.push_back(b3);
    data.push_back(b4);
    data.push_back(b5);
    data.push_back(b6);
    data.push_back(b7);
    data.push_back(crc(data));

    qCDebug(UsbIo) << "send" << data.toHex();
    int sent = device->sendFeatureReport(data.cbegin(), data.length());
    if (sent != data.length())
    {
        qCWarning(UsbIo) << "send failed: got" << sent << "expected" << data.length();
        return nullptr;
    }

    data.fill('\x0');
    int read = device->getFeatureReport(data.begin(), data.length());
    if (read != data.length())
{'fraction_non_alphanumeric': 0.12071823204419889, 'fraction_numerical': 0.025828729281767954, 'mean_word_length': 3.254406580493537, 'pattern_counts': {'":': 0, '<': 50, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3983901', 'n_tokens_mistral': 2542, 'n_tokens_neox': 2437, 'n_words': 664}