mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-12-03 11:24:10 +01:00
Merge branch 'usb_fixes' into next
This commit is contained in:
commit
2488b48f50
@ -22,8 +22,7 @@ QextSerialEnumerator::~QextSerialEnumerator( )
|
|||||||
|
|
||||||
// static
|
// static
|
||||||
QList<QextPortInfo> QextSerialEnumerator::getPorts()
|
QList<QextPortInfo> QextSerialEnumerator::getPorts()
|
||||||
{
|
{ QList<QextPortInfo> infoList;
|
||||||
QList<QextPortInfo> infoList;
|
|
||||||
io_iterator_t serialPortIterator = 0;
|
io_iterator_t serialPortIterator = 0;
|
||||||
kern_return_t kernResult = KERN_FAILURE;
|
kern_return_t kernResult = KERN_FAILURE;
|
||||||
CFMutableDictionaryRef matchingDictionary;
|
CFMutableDictionaryRef matchingDictionary;
|
||||||
@ -44,18 +43,6 @@ QList<QextPortInfo> QextSerialEnumerator::getPorts()
|
|||||||
IOObjectRelease(serialPortIterator);
|
IOObjectRelease(serialPortIterator);
|
||||||
serialPortIterator = 0;
|
serialPortIterator = 0;
|
||||||
|
|
||||||
if( !(matchingDictionary = IOServiceNameMatching("AppleUSBCDC")) ) {
|
|
||||||
qWarning("IOServiceNameMatching returned a NULL dictionary.");
|
|
||||||
return infoList;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) {
|
|
||||||
qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult;
|
|
||||||
return infoList;
|
|
||||||
}
|
|
||||||
iterateServicesOSX(serialPortIterator, infoList);
|
|
||||||
IOObjectRelease(serialPortIterator);
|
|
||||||
|
|
||||||
return infoList;
|
return infoList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,25 +44,19 @@ namespace Core {
|
|||||||
|
|
||||||
|
|
||||||
ConnectionManager::ConnectionManager(Internal::MainWindow *mainWindow, QTabWidget *modeStack) :
|
ConnectionManager::ConnectionManager(Internal::MainWindow *mainWindow, QTabWidget *modeStack) :
|
||||||
QWidget(mainWindow), // Pip
|
QWidget(mainWindow),
|
||||||
m_availableDevList(0),
|
m_availableDevList(0),
|
||||||
m_connectBtn(0),
|
m_connectBtn(0),
|
||||||
m_ioDev(NULL),
|
m_ioDev(NULL),
|
||||||
m_mainWindow(mainWindow)
|
polling(true),
|
||||||
|
m_mainWindow(mainWindow)
|
||||||
{
|
{
|
||||||
// Q_UNUSED(mainWindow);
|
|
||||||
|
|
||||||
/* QVBoxLayout *top = new QVBoxLayout;
|
|
||||||
top->setSpacing(0);
|
|
||||||
top->setMargin(0);*/
|
|
||||||
|
|
||||||
QHBoxLayout *layout = new QHBoxLayout;
|
QHBoxLayout *layout = new QHBoxLayout;
|
||||||
layout->setSpacing(5);
|
layout->setSpacing(5);
|
||||||
layout->setContentsMargins(5,5,5,5);
|
layout->setContentsMargins(5,5,5,5);
|
||||||
layout->addWidget(new QLabel(tr("Connections:")));
|
layout->addWidget(new QLabel(tr("Connections:")));
|
||||||
|
|
||||||
m_availableDevList = new QComboBox;
|
m_availableDevList = new QComboBox;
|
||||||
//m_availableDevList->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
m_availableDevList->setMinimumWidth(100);
|
m_availableDevList->setMinimumWidth(100);
|
||||||
m_availableDevList->setMaximumWidth(150);
|
m_availableDevList->setMaximumWidth(150);
|
||||||
m_availableDevList->setContextMenuPolicy(Qt::CustomContextMenu);
|
m_availableDevList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
@ -72,30 +66,25 @@ ConnectionManager::ConnectionManager(Internal::MainWindow *mainWindow, QTabWidge
|
|||||||
m_connectBtn->setEnabled(false);
|
m_connectBtn->setEnabled(false);
|
||||||
layout->addWidget(m_connectBtn);
|
layout->addWidget(m_connectBtn);
|
||||||
|
|
||||||
/* Utils::StyledBar *bar = new Utils::StyledBar;
|
|
||||||
bar->setLayout(layout);
|
|
||||||
|
|
||||||
top->addWidget(bar);*/
|
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
// modeStack->insertCornerWidget(modeStack->cornerWidgetCount()-1, this);
|
|
||||||
modeStack->setCornerWidget(this, Qt::TopRightCorner);
|
modeStack->setCornerWidget(this, Qt::TopRightCorner);
|
||||||
|
|
||||||
QObject::connect(m_connectBtn, SIGNAL(pressed()), this, SLOT(onConnectPressed()));
|
QObject::connect(m_connectBtn, SIGNAL(pressed()), this, SLOT(onConnectPressed()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionManager::~ConnectionManager()
|
ConnectionManager::~ConnectionManager()
|
||||||
{
|
{
|
||||||
disconnectDevice(); // Pip
|
disconnectDevice();
|
||||||
suspendPolling(); // Pip
|
suspendPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionManager::init()
|
void ConnectionManager::init()
|
||||||
{
|
{
|
||||||
//register to the plugin manager so we can receive
|
//register to the plugin manager so we can receive
|
||||||
//new connection object from plugins
|
//new connection object from plugins
|
||||||
QObject::connect(ExtensionSystem::PluginManager::instance(), SIGNAL(objectAdded(QObject*)), this, SLOT(objectAdded(QObject*)));
|
QObject::connect(ExtensionSystem::PluginManager::instance(), SIGNAL(objectAdded(QObject*)), this, SLOT(objectAdded(QObject*)));
|
||||||
QObject::connect(ExtensionSystem::PluginManager::instance(), SIGNAL(aboutToRemoveObject(QObject*)), this, SLOT(aboutToRemoveObject(QObject*)));
|
QObject::connect(ExtensionSystem::PluginManager::instance(), SIGNAL(aboutToRemoveObject(QObject*)), this, SLOT(aboutToRemoveObject(QObject*)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,11 +92,11 @@ void ConnectionManager::init()
|
|||||||
*/
|
*/
|
||||||
bool ConnectionManager::connectDevice()
|
bool ConnectionManager::connectDevice()
|
||||||
{
|
{
|
||||||
devListItem connection_device = findDevice(m_availableDevList->itemData(m_availableDevList->currentIndex(),Qt::ToolTipRole).toString());
|
DevListItem connection_device = findDevice(m_availableDevList->itemData(m_availableDevList->currentIndex(),Qt::ToolTipRole).toString());
|
||||||
if (!connection_device.connection)
|
if (!connection_device.connection)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QIODevice *io_dev = connection_device.connection->openDevice(connection_device.Name);
|
QIODevice *io_dev = connection_device.connection->openDevice(connection_device.device.name);
|
||||||
if (!io_dev)
|
if (!io_dev)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -115,17 +104,6 @@ bool ConnectionManager::connectDevice()
|
|||||||
|
|
||||||
// check if opening the device worked
|
// check if opening the device worked
|
||||||
if (!io_dev->isOpen()) {
|
if (!io_dev->isOpen()) {
|
||||||
qDebug() << "Error: io_dev->isOpen() returned FALSE .. could not open connection to " << connection_device.devName
|
|
||||||
<< ": " << io_dev->errorString();
|
|
||||||
|
|
||||||
// close the device
|
|
||||||
// EDOUARD: why do we close if we could not open ???
|
|
||||||
try {
|
|
||||||
connection_device.connection->closeDevice(connection_device.devName);
|
|
||||||
}
|
|
||||||
catch (...) { // handle exception
|
|
||||||
qDebug() << "Exception: connection_device.connection->closeDevice(" << connection_device.devName << ")";
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,10 +140,11 @@ bool ConnectionManager::disconnectDevice()
|
|||||||
emit deviceAboutToDisconnect();
|
emit deviceAboutToDisconnect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (m_connectionDevice.connection)
|
if (m_connectionDevice.connection) {
|
||||||
m_connectionDevice.connection->closeDevice(m_connectionDevice.devName);
|
m_connectionDevice.connection->closeDevice(m_connectionDevice.getConName());
|
||||||
|
}
|
||||||
} catch (...) { // handle exception
|
} catch (...) { // handle exception
|
||||||
qDebug() << "Exception: m_connectionDevice.connection->closeDevice(" << m_connectionDevice.devName << ")";
|
qDebug() << "Exception: m_connectionDevice.connection->closeDevice(" << m_connectionDevice.getConName() << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
m_connectionDevice.connection = NULL;
|
m_connectionDevice.connection = NULL;
|
||||||
@ -184,10 +163,7 @@ void ConnectionManager::objectAdded(QObject *obj)
|
|||||||
{
|
{
|
||||||
//Check if a plugin added a connection object to the pool
|
//Check if a plugin added a connection object to the pool
|
||||||
IConnection *connection = Aggregation::query<IConnection>(obj);
|
IConnection *connection = Aggregation::query<IConnection>(obj);
|
||||||
if (!connection) return;
|
if (!connection) return;
|
||||||
|
|
||||||
//qDebug() << "Connection object registered:" << connection->connectionName();
|
|
||||||
//qDebug() << connection->availableDevices();
|
|
||||||
|
|
||||||
//register devices and populate CB
|
//register devices and populate CB
|
||||||
devChanged(connection);
|
devChanged(connection);
|
||||||
@ -196,30 +172,30 @@ void ConnectionManager::objectAdded(QObject *obj)
|
|||||||
// to do things
|
// to do things
|
||||||
m_connectionsList.append(connection);
|
m_connectionsList.append(connection);
|
||||||
|
|
||||||
QObject::connect(connection, SIGNAL(availableDevChanged(IConnection *)), this, SLOT(devChanged(IConnection *)));
|
QObject::connect(connection, SIGNAL(availableDevChanged(IConnection *)), this, SLOT(devChanged(IConnection *)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionManager::aboutToRemoveObject(QObject *obj)
|
void ConnectionManager::aboutToRemoveObject(QObject *obj)
|
||||||
{
|
{
|
||||||
//Check if a plugin added a connection object to the pool
|
//Check if a plugin added a connection object to the pool
|
||||||
IConnection *connection = Aggregation::query<IConnection>(obj);
|
IConnection *connection = Aggregation::query<IConnection>(obj);
|
||||||
if (!connection) return;
|
if (!connection) return;
|
||||||
|
|
||||||
if (m_connectionDevice.connection && m_connectionDevice.connection == connection) // Pip
|
if (m_connectionDevice.connection && m_connectionDevice.connection == connection)
|
||||||
{ // we are currently using the one that is about to be removed
|
{ // we are currently using the one that is about to be removed
|
||||||
m_connectionDevice.connection = NULL;
|
disconnectDevice();
|
||||||
m_ioDev = NULL;
|
m_connectionDevice.connection = NULL;
|
||||||
}
|
m_ioDev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_connectionsList.contains(connection))
|
if (m_connectionsList.contains(connection))
|
||||||
m_connectionsList.removeAt(m_connectionsList.indexOf(connection));
|
m_connectionsList.removeAt(m_connectionsList.indexOf(connection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ConnectionManager::onConnectionDestroyed(QObject *obj) // Pip
|
void ConnectionManager::onConnectionDestroyed(QObject *obj)
|
||||||
{
|
{
|
||||||
Q_UNUSED(obj)
|
Q_UNUSED(obj)
|
||||||
//onConnectionClosed(obj);
|
|
||||||
disconnectDevice();
|
disconnectDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,17 +218,17 @@ void ConnectionManager::onConnectPressed()
|
|||||||
/**
|
/**
|
||||||
* Find a device by its displayed (visible on screen) name
|
* Find a device by its displayed (visible on screen) name
|
||||||
*/
|
*/
|
||||||
devListItem ConnectionManager::findDevice(const QString &devName)
|
DevListItem ConnectionManager::findDevice(const QString &devName)
|
||||||
{
|
{
|
||||||
foreach (devListItem d, m_devList)
|
foreach (DevListItem d, m_devList)
|
||||||
{
|
{
|
||||||
if (d.devName == devName)
|
if (d.getConName() == devName)
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "findDevice: cannot find " << devName << " in device list";
|
qDebug() << "findDevice: cannot find " << devName << " in device list";
|
||||||
|
|
||||||
devListItem d;
|
DevListItem d;
|
||||||
d.connection = NULL;
|
d.connection = NULL;
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
@ -264,13 +240,14 @@ devListItem ConnectionManager::findDevice(const QString &devName)
|
|||||||
*/
|
*/
|
||||||
void ConnectionManager::suspendPolling()
|
void ConnectionManager::suspendPolling()
|
||||||
{
|
{
|
||||||
foreach (IConnection *cnx, m_connectionsList)
|
foreach (IConnection *cnx, m_connectionsList)
|
||||||
{
|
{
|
||||||
cnx->suspendPolling();
|
cnx->suspendPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_connectBtn->setEnabled(false);
|
m_connectBtn->setEnabled(false);
|
||||||
m_availableDevList->setEnabled(false);
|
m_availableDevList->setEnabled(false);
|
||||||
|
polling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -279,36 +256,57 @@ void ConnectionManager::suspendPolling()
|
|||||||
*/
|
*/
|
||||||
void ConnectionManager::resumePolling()
|
void ConnectionManager::resumePolling()
|
||||||
{
|
{
|
||||||
foreach (IConnection *cnx, m_connectionsList)
|
foreach (IConnection *cnx, m_connectionsList)
|
||||||
{
|
{
|
||||||
cnx->resumePolling();
|
cnx->resumePolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_connectBtn->setEnabled(true);
|
m_connectBtn->setEnabled(true);
|
||||||
m_availableDevList->setEnabled(true);
|
m_availableDevList->setEnabled(true);
|
||||||
|
polling = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister all devices from one connection plugin
|
* Synchronize the list of connections displayed with those physically
|
||||||
* \param[in] connection Connection type that you want to forget about :)
|
* present
|
||||||
*/
|
* @param[in] connection Connection type that you want to forget about :)
|
||||||
void ConnectionManager::unregisterAll(IConnection *connection)
|
*/
|
||||||
|
void ConnectionManager::updateConnectionList(IConnection *connection)
|
||||||
{
|
{
|
||||||
for (QLinkedList<devListItem>::iterator iter = m_devList.begin(); iter != m_devList.end(); )
|
// Get the updated list of devices
|
||||||
{
|
QList <IConnection::device> availableDev = connection->availableDevices();
|
||||||
if (iter->connection == connection)
|
|
||||||
{
|
|
||||||
if (m_connectionDevice.connection && m_connectionDevice.connection == connection)
|
|
||||||
{ // we are currently using the one we are about to erase
|
|
||||||
//onConnectionClosed(m_connectionDevice.connection);
|
|
||||||
disconnectDevice();
|
|
||||||
}
|
|
||||||
|
|
||||||
iter = m_devList.erase(iter);
|
// Go through the list of connections of that type. If they are not in the
|
||||||
}
|
// available device list then remove them. If they are connected, then
|
||||||
else
|
// disconnect them.
|
||||||
++iter;
|
for (QLinkedList<DevListItem>::iterator iter = m_devList.begin(); iter != m_devList.end(); )
|
||||||
}
|
{
|
||||||
|
if (iter->connection != connection) {
|
||||||
|
++iter;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if device exists in the updated availability list
|
||||||
|
bool found = availableDev.contains(iter->device);
|
||||||
|
if (!found) {
|
||||||
|
// we are currently using the one we are about to erase
|
||||||
|
if (m_connectionDevice.connection && m_connectionDevice.connection == connection && m_connectionDevice.device == iter->device) {
|
||||||
|
disconnectDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
iter = m_devList.erase(iter);
|
||||||
|
} else
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any back to list that don't exist
|
||||||
|
foreach (IConnection::device dev, availableDev)
|
||||||
|
{
|
||||||
|
bool found = m_devList.contains(DevListItem(connection, dev));
|
||||||
|
if (!found) {
|
||||||
|
registerDevice(connection,dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -317,13 +315,9 @@ void ConnectionManager::unregisterAll(IConnection *connection)
|
|||||||
* @param disp is the name that is displayed in the dropdown menu
|
* @param disp is the name that is displayed in the dropdown menu
|
||||||
* @param name is the actual device name
|
* @param name is the actual device name
|
||||||
*/
|
*/
|
||||||
void ConnectionManager::registerDevice(IConnection *conn, const QString &devN, const QString &name, const QString &disp)
|
void ConnectionManager::registerDevice(IConnection *conn, IConnection::device device)
|
||||||
{
|
{
|
||||||
devListItem d;
|
DevListItem d(conn,device);
|
||||||
d.connection = conn;
|
|
||||||
d.devName = devN;
|
|
||||||
d.Name = name;
|
|
||||||
d.displayName=disp;
|
|
||||||
m_devList.append(d);
|
m_devList.append(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,41 +338,9 @@ void ConnectionManager::devChanged(IConnection *connection)
|
|||||||
m_availableDevList->clear();
|
m_availableDevList->clear();
|
||||||
|
|
||||||
//remove registered devices of this IConnection from the list
|
//remove registered devices of this IConnection from the list
|
||||||
unregisterAll(connection);
|
updateConnectionList(connection);
|
||||||
|
|
||||||
//and add them back in the list
|
updateConnectionDropdown();
|
||||||
QList <IConnection::device> availableDev = connection->availableDevices();
|
|
||||||
foreach (IConnection::device dev, availableDev)
|
|
||||||
{
|
|
||||||
QString cbName = connection->shortName() + ": " + dev.name;
|
|
||||||
QString disp = connection->shortName() + " : " + dev.displayName;
|
|
||||||
registerDevice(connection,cbName,dev.name,disp);
|
|
||||||
}
|
|
||||||
|
|
||||||
//add all the list again to the combobox
|
|
||||||
foreach (devListItem d, m_devList)
|
|
||||||
{
|
|
||||||
m_availableDevList->addItem(d.displayName);
|
|
||||||
m_availableDevList->setItemData(m_availableDevList->count()-1,(const QString)d.devName,Qt::ToolTipRole);
|
|
||||||
if(!m_ioDev && d.displayName.startsWith("USB"))
|
|
||||||
{
|
|
||||||
if(m_mainWindow->generalSettings()->autoConnect() || m_mainWindow->generalSettings()->autoSelect())
|
|
||||||
m_availableDevList->setCurrentIndex(m_availableDevList->count()-1);
|
|
||||||
if(m_mainWindow->generalSettings()->autoConnect())
|
|
||||||
{
|
|
||||||
connectDevice();
|
|
||||||
qDebug()<<"ConnectionManager::devChanged autoconnected USB device";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(m_ioDev)//if a device is connected make it the one selected on the dropbox
|
|
||||||
{
|
|
||||||
for(int x=0;x<m_availableDevList->count();++x)
|
|
||||||
{
|
|
||||||
if(m_connectionDevice.devName==m_availableDevList->itemData(x,Qt::ToolTipRole).toString())
|
|
||||||
m_availableDevList->setCurrentIndex(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//disable connection button if the liNameif (m_availableDevList->count() > 0)
|
//disable connection button if the liNameif (m_availableDevList->count() > 0)
|
||||||
@ -388,6 +350,33 @@ void ConnectionManager::devChanged(IConnection *connection)
|
|||||||
m_connectBtn->setEnabled(false);
|
m_connectBtn->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConnectionManager::updateConnectionDropdown()
|
||||||
|
{
|
||||||
|
//add all the list again to the combobox
|
||||||
|
foreach (DevListItem d, m_devList)
|
||||||
|
{
|
||||||
|
m_availableDevList->addItem(d.getConName());
|
||||||
|
m_availableDevList->setItemData(m_availableDevList->count()-1, d.getConName(), Qt::ToolTipRole);
|
||||||
|
if(!m_ioDev && d.getConName().startsWith("USB"))
|
||||||
|
{
|
||||||
|
if(m_mainWindow->generalSettings()->autoConnect() || m_mainWindow->generalSettings()->autoSelect())
|
||||||
|
m_availableDevList->setCurrentIndex(m_availableDevList->count()-1);
|
||||||
|
if(m_mainWindow->generalSettings()->autoConnect() && polling)
|
||||||
|
{
|
||||||
|
qDebug() << "Automatically opening device";
|
||||||
|
connectDevice();
|
||||||
|
qDebug()<<"ConnectionManager::devChanged autoconnected USB device";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(m_ioDev)//if a device is connected make it the one selected on the dropbox
|
||||||
|
{
|
||||||
|
for(int x=0;x<m_availableDevList->count();++x)
|
||||||
|
{
|
||||||
|
if(m_connectionDevice.getConName()==m_availableDevList->itemData(x,Qt::ToolTipRole).toString())
|
||||||
|
m_availableDevList->setCurrentIndex(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::ConnectionManager::connectionsCallBack()
|
void Core::ConnectionManager::connectionsCallBack()
|
||||||
@ -398,4 +387,6 @@ void Core::ConnectionManager::connectionsCallBack()
|
|||||||
}
|
}
|
||||||
connectionBackup.clear();
|
connectionBackup.clear();
|
||||||
disconnect(ExtensionSystem::PluginManager::instance(),SIGNAL(pluginsLoadEnded()),this,SLOT(connectionsCallBack()));
|
disconnect(ExtensionSystem::PluginManager::instance(),SIGNAL(pluginsLoadEnded()),this,SLOT(connectionsCallBack()));
|
||||||
|
}
|
||||||
|
|
||||||
} //namespace Core
|
} //namespace Core
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "generalsettings.h"
|
#include "generalsettings.h"
|
||||||
|
#include <coreplugin/iconnection.h>
|
||||||
#include <QtCore/QVector>
|
#include <QtCore/QVector>
|
||||||
#include <QtCore/QIODevice>
|
#include <QtCore/QIODevice>
|
||||||
#include <QtCore/QLinkedList>
|
#include <QtCore/QLinkedList>
|
||||||
@ -55,12 +56,26 @@ namespace Internal {
|
|||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
|
||||||
|
|
||||||
struct devListItem
|
class DevListItem
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
DevListItem(IConnection *c, IConnection::device d) :
|
||||||
|
connection(c), device(d) { }
|
||||||
|
|
||||||
|
DevListItem() : connection(NULL) { }
|
||||||
|
|
||||||
|
QString getConName() {
|
||||||
|
if (connection == NULL)
|
||||||
|
return "";
|
||||||
|
return connection->shortName() + ": " + device.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const DevListItem &rhs) {
|
||||||
|
return connection == rhs.connection && device == rhs.device;
|
||||||
|
}
|
||||||
|
|
||||||
IConnection *connection;
|
IConnection *connection;
|
||||||
QString devName;
|
IConnection::device device;
|
||||||
QString Name;
|
|
||||||
QString displayName;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -75,15 +90,16 @@ public:
|
|||||||
void init();
|
void init();
|
||||||
|
|
||||||
QIODevice* getCurrentConnection() { return m_ioDev; }
|
QIODevice* getCurrentConnection() { return m_ioDev; }
|
||||||
devListItem getCurrentDevice() { return m_connectionDevice;}
|
DevListItem getCurrentDevice() { return m_connectionDevice;}
|
||||||
bool disconnectDevice();
|
bool disconnectDevice();
|
||||||
void suspendPolling();
|
void suspendPolling();
|
||||||
void resumePolling();
|
void resumePolling();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void unregisterAll(IConnection *connection);
|
void updateConnectionList(IConnection *connection);
|
||||||
void registerDevice(IConnection *conn, const QString &devN, const QString &name, const QString &disp);
|
void registerDevice(IConnection *conn, IConnection::device device);
|
||||||
devListItem findDevice(const QString &devName);
|
void updateConnectionDropdown();
|
||||||
|
DevListItem findDevice(const QString &devName);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void deviceConnected(QIODevice *dev);
|
void deviceConnected(QIODevice *dev);
|
||||||
@ -96,25 +112,25 @@ private slots:
|
|||||||
void onConnectPressed();
|
void onConnectPressed();
|
||||||
void devChanged(IConnection *connection);
|
void devChanged(IConnection *connection);
|
||||||
|
|
||||||
// void onConnectionClosed(QObject *obj);
|
|
||||||
void onConnectionDestroyed(QObject *obj);
|
void onConnectionDestroyed(QObject *obj);
|
||||||
void connectionsCallBack(); //used to call devChange after all the plugins are loaded
|
void connectionsCallBack(); //used to call devChange after all the plugins are loaded
|
||||||
protected:
|
protected:
|
||||||
QComboBox *m_availableDevList;
|
QComboBox *m_availableDevList;
|
||||||
QPushButton *m_connectBtn;
|
QPushButton *m_connectBtn;
|
||||||
QLinkedList<devListItem> m_devList;
|
QLinkedList<DevListItem> m_devList;
|
||||||
QList<IConnection*> m_connectionsList;
|
QList<IConnection*> m_connectionsList;
|
||||||
|
|
||||||
//currently connected connection plugin
|
//currently connected connection plugin
|
||||||
devListItem m_connectionDevice;
|
DevListItem m_connectionDevice;
|
||||||
|
|
||||||
//currently connected QIODevice
|
//currently connected QIODevice
|
||||||
QIODevice *m_ioDev;
|
QIODevice *m_ioDev;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool connectDevice();
|
bool connectDevice();
|
||||||
Internal::MainWindow *m_mainWindow;
|
bool polling;
|
||||||
QList <IConnection *> connectionBackup;
|
Internal::MainWindow *m_mainWindow;
|
||||||
|
QList <IConnection *> connectionBackup;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ QList <Core::IConnection::device> IPconnectionConnection::availableDevices()
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
QIODevice *IPconnectionConnection::openDevice(const QString &deviceName)
|
QIODevice *IPconnectionConnection::openDevice(const QString &)
|
||||||
{
|
{
|
||||||
Q_UNUSED(deviceName);
|
Q_UNUSED(deviceName);
|
||||||
|
|
||||||
@ -210,7 +210,7 @@ QIODevice *IPconnectionConnection::openDevice(const QString &deviceName)
|
|||||||
return ipSocket;
|
return ipSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPconnectionConnection::closeDevice(const QString &deviceName)
|
void IPconnectionConnection::closeDevice(const QString &)
|
||||||
{
|
{
|
||||||
Q_UNUSED(deviceName);
|
Q_UNUSED(deviceName);
|
||||||
|
|
||||||
|
@ -32,11 +32,15 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QMutex>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include "rawhid_global.h"
|
#include "rawhid_global.h"
|
||||||
|
|
||||||
#if defined( Q_OS_MAC)
|
#if defined( Q_OS_MAC)
|
||||||
|
|
||||||
|
#include <IOKit/IOKitLib.h>
|
||||||
|
#include <IOKit/hid/IOHIDLib.h>
|
||||||
|
#include <CoreFoundation/CFString.h>
|
||||||
|
|
||||||
#elif defined(Q_OS_UNIX)
|
#elif defined(Q_OS_UNIX)
|
||||||
//#elif defined(Q_OS_LINUX)
|
//#elif defined(Q_OS_LINUX)
|
||||||
@ -96,22 +100,43 @@ public:
|
|||||||
pjrc_rawhid();
|
pjrc_rawhid();
|
||||||
~pjrc_rawhid();
|
~pjrc_rawhid();
|
||||||
int open(int max, int vid, int pid, int usage_page, int usage);
|
int open(int max, int vid, int pid, int usage_page, int usage);
|
||||||
int receive(int num, void *buf, int len, int timeout);
|
int receive(int, void *buf, int len, int timeout);
|
||||||
void close(int num);
|
void close(int num);
|
||||||
int send(int num, void *buf, int len, int timeout);
|
int send(int num, void *buf, int len, int timeout);
|
||||||
QString getserial(int num);
|
QString getserial(int num);
|
||||||
void mytest(int num);
|
|
||||||
signals:
|
signals:
|
||||||
void deviceUnplugged(int);//just to make pips changes compile
|
void deviceUnplugged(int);
|
||||||
#if defined( Q_OS_MAC)
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if defined( Q_OS_MAC)
|
#if defined( Q_OS_MAC)
|
||||||
|
|
||||||
|
// Static callbacks called by the HID system with handles to the PJRC object
|
||||||
|
static void attach_callback(void *, IOReturn, void *, IOHIDDeviceRef);
|
||||||
|
static void dettach_callback(void *, IOReturn, void *hid_mgr, IOHIDDeviceRef dev);
|
||||||
|
static void input_callback(void *, IOReturn, void *, IOHIDReportType, uint32_t, uint8_t *, CFIndex);
|
||||||
|
static void timeout_callback(CFRunLoopTimerRef, void *);
|
||||||
|
|
||||||
|
// Non static methods to call into
|
||||||
|
void attach(IOHIDDeviceRef dev);
|
||||||
|
void dettach(IOHIDDeviceRef dev);
|
||||||
|
void input(uint8_t *, CFIndex);
|
||||||
|
|
||||||
|
// Platform specific handles for the USB device
|
||||||
|
IOHIDManagerRef hid_manager;
|
||||||
|
IOHIDDeviceRef dev;
|
||||||
|
CFRunLoopRef the_correct_runloop;
|
||||||
|
CFRunLoopRef received_runloop;
|
||||||
|
|
||||||
|
static const int BUFFER_SIZE = 64;
|
||||||
|
uint8_t buffer[BUFFER_SIZE];
|
||||||
|
int attach_count;
|
||||||
|
int buffer_count;
|
||||||
|
bool device_open;
|
||||||
|
bool unplugged;
|
||||||
|
|
||||||
|
QMutex *m_writeMutex;
|
||||||
|
QMutex *m_readMutex;
|
||||||
#elif defined(Q_OS_UNIX)
|
#elif defined(Q_OS_UNIX)
|
||||||
//#elif defined(Q_OS_LINUX)
|
|
||||||
|
|
||||||
hid_t *first_hid;
|
hid_t *first_hid;
|
||||||
hid_t *last_hid;
|
hid_t *last_hid;
|
||||||
|
@ -40,102 +40,67 @@
|
|||||||
#include "pjrc_rawhid.h"
|
#include "pjrc_rawhid.h"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <IOKit/IOKitLib.h>
|
|
||||||
#include <IOKit/hid/IOHIDLib.h>
|
|
||||||
#include <CoreFoundation/CFString.h>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
class delay : public QThread
|
struct timeout_info {
|
||||||
|
CFRunLoopRef loopRef;
|
||||||
|
bool timed_out;
|
||||||
|
};
|
||||||
|
|
||||||
|
pjrc_rawhid::pjrc_rawhid() :
|
||||||
|
device_open(false), hid_manager(NULL), buffer_count(0), unplugged(false)
|
||||||
{
|
{
|
||||||
public:
|
m_writeMutex = new QMutex();
|
||||||
static void msleep(unsigned long msecs)
|
m_readMutex = new QMutex();
|
||||||
{
|
|
||||||
QThread::msleep(msecs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#define BUFFER_SIZE 64
|
|
||||||
|
|
||||||
//#define printf qDebug
|
|
||||||
#define printf
|
|
||||||
|
|
||||||
typedef struct hid_struct hid_t;
|
|
||||||
typedef struct buffer_struct buffer_t;
|
|
||||||
static hid_t *first_hid = NULL;
|
|
||||||
static hid_t *last_hid = NULL;
|
|
||||||
// Make sure we use the correct runloop
|
|
||||||
CFRunLoopRef the_correct_runloop = NULL;
|
|
||||||
struct hid_struct {
|
|
||||||
IOHIDDeviceRef ref;
|
|
||||||
int open;
|
|
||||||
uint8_t buffer[BUFFER_SIZE];
|
|
||||||
buffer_t *first_buffer;
|
|
||||||
buffer_t *last_buffer;
|
|
||||||
struct hid_struct *prev;
|
|
||||||
struct hid_struct *next;
|
|
||||||
};
|
|
||||||
struct buffer_struct {
|
|
||||||
struct buffer_struct *next;
|
|
||||||
uint32_t len;
|
|
||||||
uint8_t buf[BUFFER_SIZE];
|
|
||||||
};
|
|
||||||
|
|
||||||
static void add_hid(hid_t *);
|
|
||||||
static hid_t * get_hid(int);
|
|
||||||
static void free_all_hid(void);
|
|
||||||
static void hid_close(hid_t *);
|
|
||||||
static void attach_callback(void *, IOReturn, void *, IOHIDDeviceRef);
|
|
||||||
static void detach_callback(void *, IOReturn, void *hid_mgr, IOHIDDeviceRef dev);
|
|
||||||
static void input_callback(void *, IOReturn, void *, IOHIDReportType, uint32_t, uint8_t *, CFIndex);
|
|
||||||
static void output_callback(hid_t *context, IOReturn ret, void *sender, IOHIDReportType type, uint32_t id, uint8_t *data, CFIndex len);
|
|
||||||
static void timeout_callback(CFRunLoopTimerRef, void *);
|
|
||||||
|
|
||||||
|
|
||||||
pjrc_rawhid::pjrc_rawhid()
|
|
||||||
{
|
|
||||||
first_hid = NULL;
|
|
||||||
last_hid = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pjrc_rawhid::~pjrc_rawhid()
|
pjrc_rawhid::~pjrc_rawhid()
|
||||||
{
|
{
|
||||||
|
if (device_open) {
|
||||||
|
close(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_writeMutex) {
|
||||||
|
delete m_writeMutex;
|
||||||
|
m_writeMutex = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_readMutex) {
|
||||||
|
delete m_readMutex;
|
||||||
|
m_readMutex = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// open - open 1 or more devices
|
/**
|
||||||
//
|
* @brief open - open 1 or more devices
|
||||||
// Inputs:
|
* @param[in] max maximum number of devices to open
|
||||||
// max = maximum number of devices to open
|
* @param[in] vid Vendor ID, or -1 if any
|
||||||
// vid = Vendor ID, or -1 if any
|
* @param[in] pid Product ID, or -1 if any
|
||||||
// pid = Product ID, or -1 if any
|
* @param[in] usage_page top level usage page, or -1 if any
|
||||||
// usage_page = top level usage page, or -1 if any
|
* @param[in] usage top level usage number, or -1 if any
|
||||||
// usage = top level usage number, or -1 if any
|
* @returns actual number of devices opened
|
||||||
// Output:
|
*/
|
||||||
// actual number of devices opened
|
|
||||||
//
|
|
||||||
int pjrc_rawhid::open(int max, int vid, int pid, int usage_page, int usage)
|
int pjrc_rawhid::open(int max, int vid, int pid, int usage_page, int usage)
|
||||||
{
|
{
|
||||||
static IOHIDManagerRef hid_manager=NULL;
|
|
||||||
CFMutableDictionaryRef dict;
|
CFMutableDictionaryRef dict;
|
||||||
CFNumberRef num;
|
CFNumberRef num;
|
||||||
IOReturn ret;
|
IOReturn ret;
|
||||||
hid_t *p;
|
|
||||||
int count=0;
|
|
||||||
|
|
||||||
if (first_hid) free_all_hid();
|
Q_ASSERT(hid_manager == NULL);
|
||||||
//printf("pjrc_rawhid_open, max=%d\n", max);
|
Q_ASSERT(device_open == false);
|
||||||
if (max < 1) return 0;
|
|
||||||
|
attach_count = 0;
|
||||||
|
|
||||||
// Start the HID Manager
|
// Start the HID Manager
|
||||||
// http://developer.apple.com/technotes/tn2007/tn2187.html
|
hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
||||||
if (!hid_manager) {
|
if (hid_manager == NULL || CFGetTypeID(hid_manager) != IOHIDManagerGetTypeID()) {
|
||||||
hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
if (hid_manager) CFRelease(hid_manager);
|
||||||
if (hid_manager == NULL || CFGetTypeID(hid_manager) != IOHIDManagerGetTypeID()) {
|
return 0;
|
||||||
if (hid_manager) CFRelease(hid_manager);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vid > 0 || pid > 0 || usage_page > 0 || usage > 0) {
|
if (vid > 0 || pid > 0 || usage_page > 0 || usage > 0) {
|
||||||
// Tell the HID Manager what type of devices we want
|
// Tell the HID Manager what type of devices we want
|
||||||
dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
|
dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
|
||||||
@ -166,162 +131,147 @@ int pjrc_rawhid::open(int max, int vid, int pid, int usage_page, int usage)
|
|||||||
} else {
|
} else {
|
||||||
IOHIDManagerSetDeviceMatching(hid_manager, NULL);
|
IOHIDManagerSetDeviceMatching(hid_manager, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the run loop reference before configuring the attach callback
|
||||||
|
the_correct_runloop = CFRunLoopGetCurrent();
|
||||||
|
|
||||||
// set up a callbacks for device attach & detach
|
// set up a callbacks for device attach & detach
|
||||||
IOHIDManagerScheduleWithRunLoop(hid_manager, CFRunLoopGetCurrent(),
|
IOHIDManagerScheduleWithRunLoop(hid_manager, CFRunLoopGetCurrent(),
|
||||||
kCFRunLoopDefaultMode);
|
kCFRunLoopDefaultMode);
|
||||||
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, attach_callback, NULL);
|
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, pjrc_rawhid::attach_callback, this);
|
||||||
IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, detach_callback, NULL);
|
IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, pjrc_rawhid::dettach_callback, this);
|
||||||
ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone);
|
ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone);
|
||||||
if (ret != kIOReturnSuccess) {
|
if (ret != kIOReturnSuccess) {
|
||||||
printf("Could not start IOHIDManager");
|
|
||||||
IOHIDManagerUnscheduleFromRunLoop(hid_manager,
|
IOHIDManagerUnscheduleFromRunLoop(hid_manager,
|
||||||
CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||||
CFRelease(hid_manager);
|
CFRelease(hid_manager);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// Set the run loop reference:
|
|
||||||
the_correct_runloop = CFRunLoopGetCurrent();
|
|
||||||
printf("run loop\n");
|
|
||||||
// let it do the callback for all devices
|
// let it do the callback for all devices
|
||||||
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource) ;
|
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource) ;
|
||||||
|
|
||||||
// count up how many were added by the callback
|
// count up how many were added by the callback
|
||||||
for (p = first_hid; p; p = p->next) count++;
|
return attach_count;
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recveive - receive a packet
|
/**
|
||||||
// Inputs:
|
* @brief receive - receive a packet
|
||||||
// num = device to receive from (zero based)
|
* @param[in] num device to receive from (unused now)
|
||||||
// buf = buffer to receive packet
|
* @param[in] buf buffer to receive packet
|
||||||
// len = buffer's size
|
* @param[in] len buffer's size
|
||||||
// timeout = time to wait, in milliseconds
|
* @param[in] timeout = time to wait, in milliseconds
|
||||||
// Output:
|
* @returns number of bytes received, or -1 on error
|
||||||
// number of bytes received, or -1 on error
|
*/
|
||||||
//
|
int pjrc_rawhid::receive(int, void *buf, int len, int timeout)
|
||||||
int pjrc_rawhid::receive(int num, void *buf, int len, int timeout)
|
|
||||||
{
|
{
|
||||||
hid_t *hid;
|
QMutexLocker locker(m_readMutex);
|
||||||
buffer_t *b;
|
Q_UNUSED(locker);
|
||||||
CFRunLoopTimerRef timer=NULL;
|
|
||||||
CFRunLoopTimerContext context;
|
|
||||||
int ret=0, timeout_occurred=0;
|
|
||||||
|
|
||||||
if (len < 1) return 0;
|
if (!device_open)
|
||||||
hid = get_hid(num);
|
return -1;
|
||||||
if (!hid || !hid->open) return -1;
|
|
||||||
if ((b = hid->first_buffer) != NULL) {
|
|
||||||
if (len > b->len) len = b->len;
|
|
||||||
memcpy(buf, b->buf, len);
|
|
||||||
hid->first_buffer = b->next;
|
|
||||||
free(b);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
memset(&context, 0, sizeof(context));
|
|
||||||
context.info = &timeout_occurred;
|
|
||||||
timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() +
|
|
||||||
(double)timeout / 1000.0, 0, 0, 0, timeout_callback, &context);
|
|
||||||
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
|
|
||||||
the_correct_runloop = CFRunLoopGetCurrent();
|
|
||||||
//qDebug("--");
|
|
||||||
while (1) {
|
|
||||||
//qDebug(".");
|
|
||||||
CFRunLoopRun(); // Found the problem: somehow the input_callback does not
|
|
||||||
// stop this CFRunLoopRun because it is hooked to a different run loop !!!
|
|
||||||
// Hence the use of the "correct_runloop" variable above.
|
|
||||||
//qDebug(" ..");
|
|
||||||
|
|
||||||
if ((b = hid->first_buffer) != NULL) {
|
// Pass information to the callback to stop this run loop and signal if a timeout occurred
|
||||||
if (len > b->len) len = b->len;
|
struct timeout_info info;
|
||||||
memcpy(buf, b->buf, len);
|
info.loopRef = CFRunLoopGetCurrent();;
|
||||||
hid->first_buffer = b->next;
|
info.timed_out = false;
|
||||||
free(b);
|
CFRunLoopTimerContext context;
|
||||||
ret = len;
|
memset(&context, 0, sizeof(context));
|
||||||
//qDebug("*************");
|
context.info = &info;
|
||||||
break;
|
|
||||||
}
|
// Set up the timer for the timeout
|
||||||
if (!hid->open) {
|
CFRunLoopTimerRef timer;
|
||||||
printf("pjrc_rawhid_recv, device not open\n");
|
timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + (double)timeout / 1000.0, 0, 0, 0, timeout_callback, &context);
|
||||||
ret = -1;
|
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
|
||||||
break;
|
|
||||||
}
|
received_runloop = CFRunLoopGetCurrent();
|
||||||
if (timeout_occurred)
|
|
||||||
|
// Run the CFRunLoop until either a timeout or data is available
|
||||||
|
while(1) {
|
||||||
|
if (buffer_count != 0) {
|
||||||
|
if (len > buffer_count) len = buffer_count;
|
||||||
|
memcpy(buf, buffer, len);
|
||||||
|
buffer_count = 0;
|
||||||
break;
|
break;
|
||||||
}
|
} else if (info.timed_out) {
|
||||||
CFRunLoopTimerInvalidate(timer);
|
len = 0;
|
||||||
CFRelease(timer);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send - send a packet
|
|
||||||
// Inputs:
|
|
||||||
// num = device to transmit to (zero based)
|
|
||||||
// buf = buffer containing packet to send
|
|
||||||
// len = number of bytes to transmit
|
|
||||||
// timeout = time to wait, in milliseconds
|
|
||||||
// Output:
|
|
||||||
// number of bytes sent, or -1 on error
|
|
||||||
//
|
|
||||||
int pjrc_rawhid::send(int num, void *buf, int len, int timeout)
|
|
||||||
{
|
|
||||||
hid_t *hid;
|
|
||||||
int result=-100;
|
|
||||||
|
|
||||||
hid = get_hid(num);
|
|
||||||
if (!hid || !hid->open) return -1;
|
|
||||||
#if 1
|
|
||||||
#warning "Send timeout not implemented on MACOSX"
|
|
||||||
uint8_t *report_buf = (uint8_t *) malloc(len);
|
|
||||||
memcpy(&report_buf[0], buf,len);
|
|
||||||
// Note: packet processing done in OS indepdent code
|
|
||||||
IOReturn ret = IOHIDDeviceSetReport(hid->ref, kIOHIDReportTypeOutput, 2, (uint8_t *)report_buf, len);
|
|
||||||
result = (ret == kIOReturnSuccess) ? len : -1;
|
|
||||||
if (err_get_system(ret) == err_get_system(sys_iokit))
|
|
||||||
{
|
|
||||||
|
|
||||||
// The error was in the I/O Kit system
|
|
||||||
UInt32 codeValue = err_get_code(ret);
|
|
||||||
qDebug("Returned: %x", codeValue);
|
|
||||||
// Can now perform test on error code, display it to user, or whatever.
|
|
||||||
usleep(1000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
#if 0
|
|
||||||
// No matter what I tried this never actually sends an output
|
|
||||||
// report and output_callback never gets called. Why??
|
|
||||||
// Did I miss something? This is exactly the same params as
|
|
||||||
// the sync call that works. Is it an Apple bug?
|
|
||||||
// (submitted to Apple on 22-sep-2009, problem ID 7245050)
|
|
||||||
//
|
|
||||||
IOHIDDeviceScheduleWithRunLoop(hid->ref, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
|
||||||
// should already be scheduled with run loop by attach_callback,
|
|
||||||
// sadly this doesn't make any difference either way
|
|
||||||
//
|
|
||||||
IOHIDDeviceSetReportWithCallback(hid->ref, kIOHIDReportTypeOutput,
|
|
||||||
0, buf, len, (double)timeout / 1000.0, output_callback, &result);
|
|
||||||
while (1) {
|
|
||||||
printf("enter run loop (send)\n");
|
|
||||||
CFRunLoopRun();
|
|
||||||
printf("leave run loop (send)\n");
|
|
||||||
if (result > -100) break;
|
|
||||||
if (!hid->open) {
|
|
||||||
result = -1;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
CFRunLoopRun(); // Wait for data
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return result;
|
CFRunLoopTimerInvalidate(timer);
|
||||||
|
CFRelease(timer);
|
||||||
|
|
||||||
|
received_runloop = NULL;
|
||||||
|
|
||||||
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper class that will workaround the fact
|
||||||
|
* that the HID send is broken on OSX
|
||||||
|
*/
|
||||||
|
class Sender : public QThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Sender(IOHIDDeviceRef d, uint8_t * b, int l) :
|
||||||
|
dev(d), buf(b), len(l), result(-1) { }
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
ret = IOHIDDeviceSetReport(dev, kIOHIDReportTypeOutput, 2, buf, len);
|
||||||
|
result = (ret == kIOReturnSuccess) ? len : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result;
|
||||||
|
IOReturn ret;
|
||||||
|
private:
|
||||||
|
IOHIDDeviceRef dev;
|
||||||
|
uint8_t * buf;
|
||||||
|
int len;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief send - send a packet
|
||||||
|
* @param[in] num device to transmit to (zero based)
|
||||||
|
* @param[in] buf buffer containing packet to send
|
||||||
|
* @param[in] len number of bytes to transmit
|
||||||
|
* @param[in] timeout = time to wait, in milliseconds
|
||||||
|
* @returns number of bytes sent, or -1 on error
|
||||||
|
*/
|
||||||
|
int pjrc_rawhid::send(int, void *buf, int len, int timeout)
|
||||||
|
{
|
||||||
|
// This lock ensures that when closing we don't do it until the
|
||||||
|
// write has terminated (and then the device_open flag is set to false)
|
||||||
|
QMutexLocker locker(m_writeMutex);
|
||||||
|
Q_UNUSED(locker);
|
||||||
|
|
||||||
|
if(!device_open || unplugged) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *report_buf = (uint8_t *) malloc(len);
|
||||||
|
memcpy(&report_buf[0], buf,len);
|
||||||
|
|
||||||
|
QEventLoop el;
|
||||||
|
Sender sender(dev, report_buf, len);
|
||||||
|
connect(&sender, SIGNAL(finished()), &el, SLOT(quit()));
|
||||||
|
sender.start();
|
||||||
|
QTimer::singleShot(timeout, &el, SLOT(quit()));
|
||||||
|
el.exec();
|
||||||
|
|
||||||
|
return sender.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Get the serial number for a HID device
|
||||||
QString pjrc_rawhid::getserial(int num) {
|
QString pjrc_rawhid::getserial(int num) {
|
||||||
hid_t *hid;
|
QMutexLocker locker(m_readMutex);
|
||||||
char buf[128];
|
Q_UNUSED(locker);
|
||||||
|
|
||||||
hid = get_hid(num);
|
if (!device_open || unplugged)
|
||||||
|
return "";
|
||||||
|
|
||||||
if (!hid || !hid->open) return QString("Error");
|
CFTypeRef serialnum = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDSerialNumberKey));
|
||||||
|
|
||||||
CFTypeRef serialnum = IOHIDDeviceGetProperty(hid->ref, CFSTR(kIOHIDSerialNumberKey));
|
|
||||||
if(serialnum && CFGetTypeID(serialnum) == CFStringGetTypeID())
|
if(serialnum && CFGetTypeID(serialnum) == CFStringGetTypeID())
|
||||||
{
|
{
|
||||||
//Note: I'm not sure it will always succeed if encoded as MacRoman but that
|
//Note: I'm not sure it will always succeed if encoded as MacRoman but that
|
||||||
@ -334,149 +284,106 @@ QString pjrc_rawhid::getserial(int num) {
|
|||||||
return QString("Error");
|
return QString("Error");
|
||||||
}
|
}
|
||||||
|
|
||||||
// close - close a device
|
//! Close the HID device
|
||||||
//
|
void pjrc_rawhid::close(int)
|
||||||
// Inputs:
|
|
||||||
// num = device to close (zero based)
|
|
||||||
// Output
|
|
||||||
// (nothing)
|
|
||||||
//
|
|
||||||
void pjrc_rawhid::close(int num)
|
|
||||||
{
|
{
|
||||||
hid_t *hid;
|
// Make sure any pending locks are done
|
||||||
|
QMutexLocker lock(m_writeMutex);
|
||||||
|
|
||||||
hid = get_hid(num);
|
if (device_open) {
|
||||||
if (!hid || !hid->open) return;
|
device_open = false;
|
||||||
hid_close(hid);
|
CFRunLoopStop(the_correct_runloop);
|
||||||
hid->open = 0;
|
|
||||||
|
if (!unplugged) {
|
||||||
|
IOHIDDeviceUnscheduleFromRunLoop(dev, the_correct_runloop, kCFRunLoopDefaultMode);
|
||||||
|
IOHIDDeviceRegisterInputReportCallback(dev, buffer, sizeof(buffer), NULL, NULL);
|
||||||
|
IOHIDDeviceClose(dev, kIOHIDOptionsTypeNone);
|
||||||
|
}
|
||||||
|
|
||||||
|
IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, NULL, NULL);
|
||||||
|
IOHIDManagerClose(hid_manager, 0);
|
||||||
|
|
||||||
|
dev = NULL;
|
||||||
|
hid_manager = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
/**
|
||||||
//
|
* @brief input Called to add input data to the buffer
|
||||||
// Private Functions
|
* @param[in] id Report id
|
||||||
//
|
* @param[in] data The data buffer
|
||||||
//
|
* @param[in] len The report length
|
||||||
static void input_callback(void *context, IOReturn ret, void *sender, IOHIDReportType type, uint32_t id, uint8_t *data, CFIndex len)
|
*/
|
||||||
|
void pjrc_rawhid::input(uint8_t *data, CFIndex len)
|
||||||
{
|
{
|
||||||
buffer_t *n;
|
if (!device_open)
|
||||||
hid_t *hid;
|
return;
|
||||||
|
|
||||||
//qDebug("input_callback, ret: %i - report id: %i buf: %x %x, len: %d\n", ret, id, data[0], data[1], len);
|
|
||||||
if (ret != kIOReturnSuccess || len < 1) return;
|
|
||||||
hid = (hid_t*)context;
|
|
||||||
if (!hid || hid->ref != sender) return;
|
|
||||||
printf("Processing packet");
|
|
||||||
n = (buffer_t *)malloc(sizeof(buffer_t));
|
|
||||||
if (!n) return;
|
|
||||||
if (len > BUFFER_SIZE) len = BUFFER_SIZE;
|
if (len > BUFFER_SIZE) len = BUFFER_SIZE;
|
||||||
// Note: packet preprocessing done in OS independent code
|
// Note: packet preprocessing done in OS independent code
|
||||||
memcpy(n->buf, &data[0], len);
|
memcpy(buffer, &data[0], len);
|
||||||
n->len = len;
|
buffer_count = len;
|
||||||
n->next = NULL;
|
|
||||||
if (!hid->first_buffer || !hid->last_buffer) {
|
if (received_runloop)
|
||||||
hid->first_buffer = hid->last_buffer = n;
|
CFRunLoopStop(received_runloop);
|
||||||
} else {
|
|
||||||
hid->last_buffer->next = n;
|
|
||||||
hid->last_buffer = n;
|
|
||||||
}
|
|
||||||
//qDebug() << "Stop CFRunLoop from input_callback" << CFRunLoopGetCurrent();
|
|
||||||
CFRunLoopStop(the_correct_runloop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void timeout_callback(CFRunLoopTimerRef timer, void *info)
|
//! Callback for the HID driver on an input report
|
||||||
|
void pjrc_rawhid::input_callback(void *c, IOReturn ret, void *sender, IOHIDReportType type, uint32_t id, uint8_t *data, CFIndex len)
|
||||||
{
|
{
|
||||||
//qDebug("timeout_callback\n");
|
if (ret != kIOReturnSuccess || len < 1) return;
|
||||||
*(int *)info = 1;
|
|
||||||
//qDebug() << "Stop CFRunLoop from timeout_callback" << CFRunLoopGetCurrent();
|
pjrc_rawhid *context = (pjrc_rawhid *) c;
|
||||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
context->input(data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_hid(hid_t *h)
|
//! Timeout used for the
|
||||||
|
void pjrc_rawhid::timeout_callback(CFRunLoopTimerRef, void *i)
|
||||||
{
|
{
|
||||||
if (!first_hid || !last_hid) {
|
struct timeout_info *info = (struct timeout_info *) i;
|
||||||
first_hid = last_hid = h;
|
info->timed_out = true;
|
||||||
h->next = h->prev = NULL;
|
CFRunLoopStop(info->loopRef);
|
||||||
return;
|
|
||||||
}
|
|
||||||
last_hid->next = h;
|
|
||||||
h->prev = last_hid;
|
|
||||||
h->next = NULL;
|
|
||||||
last_hid = h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Called on a dettach event
|
||||||
static hid_t * get_hid(int num)
|
void pjrc_rawhid::dettach(IOHIDDeviceRef d)
|
||||||
{
|
{
|
||||||
hid_t *p;
|
unplugged = true;
|
||||||
for (p = first_hid; p && num > 0; p = p->next, num--) ;
|
if (d == dev)
|
||||||
return p;
|
emit deviceUnplugged(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Called from the USB system and forwarded to the instance (context)
|
||||||
static void free_all_hid(void)
|
void pjrc_rawhid::dettach_callback(void *context, IOReturn, void *, IOHIDDeviceRef dev)
|
||||||
{
|
{
|
||||||
hid_t *p, *q;
|
pjrc_rawhid *p = (pjrc_rawhid*) context;
|
||||||
|
p->dettach(dev);
|
||||||
for (p = first_hid; p; p = p->next) {
|
|
||||||
hid_close(p);
|
|
||||||
}
|
|
||||||
p = first_hid;
|
|
||||||
while (p) {
|
|
||||||
q = p;
|
|
||||||
p = p->next;
|
|
||||||
free(q);
|
|
||||||
}
|
|
||||||
first_hid = last_hid = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
static void hid_close(hid_t *hid)
|
* @brief Called by the USB system
|
||||||
|
* @param dev The device that was attached
|
||||||
|
*/
|
||||||
|
void pjrc_rawhid::attach(IOHIDDeviceRef d)
|
||||||
{
|
{
|
||||||
if (!hid || !hid->open || !hid->ref) return;
|
// Store the device handle
|
||||||
IOHIDDeviceUnscheduleFromRunLoop(hid->ref, CFRunLoopGetCurrent( ), kCFRunLoopDefaultMode);
|
dev = d;
|
||||||
IOHIDDeviceClose(hid->ref, kIOHIDOptionsTypeNone);
|
|
||||||
hid->ref = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void detach_callback(void *context, IOReturn r, void *hid_mgr, IOHIDDeviceRef dev)
|
|
||||||
{
|
|
||||||
hid_t *p;
|
|
||||||
|
|
||||||
printf("detach callback\n");
|
|
||||||
for (p = first_hid; p; p = p->next) {
|
|
||||||
if (p->ref == dev) {
|
|
||||||
p->open = 0;
|
|
||||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void attach_callback(void *context, IOReturn r, void *hid_mgr, IOHIDDeviceRef dev)
|
|
||||||
{
|
|
||||||
struct hid_struct *h;
|
|
||||||
|
|
||||||
printf("attach callback\n");
|
|
||||||
if (IOHIDDeviceOpen(dev, kIOHIDOptionsTypeNone) != kIOReturnSuccess) return;
|
if (IOHIDDeviceOpen(dev, kIOHIDOptionsTypeNone) != kIOReturnSuccess) return;
|
||||||
h = (hid_t *)malloc(sizeof(hid_t));
|
// Disconnect the attach callback since we don't want to automatically reconnect
|
||||||
if (!h) return;
|
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, NULL, NULL);
|
||||||
memset(h, 0, sizeof(hid_t));
|
IOHIDDeviceScheduleWithRunLoop(dev, the_correct_runloop, kCFRunLoopDefaultMode);
|
||||||
IOHIDDeviceScheduleWithRunLoop(dev, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
IOHIDDeviceRegisterInputReportCallback(dev, buffer, sizeof(buffer), pjrc_rawhid::input_callback, this);
|
||||||
IOHIDDeviceRegisterInputReportCallback(dev, h->buffer, sizeof(h->buffer), input_callback, h);
|
|
||||||
h->ref = dev;
|
attach_count++;
|
||||||
h->open = 1;
|
device_open = true;
|
||||||
add_hid(h);
|
unplugged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void output_callback(hid_t *context, IOReturn ret, void *sender, IOHIDReportType type, uint32_t id, uint8_t *data, CFIndex len)
|
//! Called from the USB system and forwarded to the instance (context)
|
||||||
|
void pjrc_rawhid::attach_callback(void *context, IOReturn r, void *hid_mgr, IOHIDDeviceRef dev)
|
||||||
{
|
{
|
||||||
printf("output_callback, r=%d\n", ret);
|
pjrc_rawhid *p = (pjrc_rawhid*) context;
|
||||||
if (ret == kIOReturnSuccess) {
|
p->attach(dev);
|
||||||
*(int *)context = len;
|
|
||||||
} else {
|
|
||||||
// timeout if not success?
|
|
||||||
*(int *)context = 0;
|
|
||||||
}
|
|
||||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,19 +138,20 @@ RawHIDReadThread::RawHIDReadThread(RawHID *hid)
|
|||||||
hidno(hid->m_deviceNo),
|
hidno(hid->m_deviceNo),
|
||||||
m_running(true)
|
m_running(true)
|
||||||
{
|
{
|
||||||
|
hid->m_startedMutex->lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
RawHIDReadThread::~RawHIDReadThread()
|
RawHIDReadThread::~RawHIDReadThread()
|
||||||
{
|
{
|
||||||
m_running = false;
|
m_running = false;
|
||||||
//wait for the thread to terminate
|
//wait for the thread to terminate
|
||||||
if(wait(1000) == false)
|
if(wait(10000) == false)
|
||||||
qDebug() << "Cannot terminate RawHIDReadThread";
|
qDebug() << "Cannot terminate RawHIDReadThread";
|
||||||
}
|
}
|
||||||
|
|
||||||
void RawHIDReadThread::run()
|
void RawHIDReadThread::run()
|
||||||
{
|
{
|
||||||
qDebug() << "Read thread started";
|
m_running = m_hid->openDevice();
|
||||||
while(m_running)
|
while(m_running)
|
||||||
{
|
{
|
||||||
//here we use a temporary buffer so we don't need to lock
|
//here we use a temporary buffer so we don't need to lock
|
||||||
@ -182,6 +183,7 @@ void RawHIDReadThread::run()
|
|||||||
m_running=false;
|
m_running=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m_hid->closeDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
int RawHIDReadThread::getReadData(char *data, int size)
|
int RawHIDReadThread::getReadData(char *data, int size)
|
||||||
@ -216,13 +218,12 @@ RawHIDWriteThread::~RawHIDWriteThread()
|
|||||||
{
|
{
|
||||||
m_running = false;
|
m_running = false;
|
||||||
//wait for the thread to terminate
|
//wait for the thread to terminate
|
||||||
if(wait(1000) == false)
|
if(wait(10000) == false)
|
||||||
qDebug() << "Cannot terminate RawHIDReadThread";
|
qDebug() << "Cannot terminate RawHIDReadThread";
|
||||||
}
|
}
|
||||||
|
|
||||||
void RawHIDWriteThread::run()
|
void RawHIDWriteThread::run()
|
||||||
{
|
{
|
||||||
qDebug() << "Write thread started";
|
|
||||||
while(m_running)
|
while(m_running)
|
||||||
{
|
{
|
||||||
char buffer[WRITE_SIZE] = {0};
|
char buffer[WRITE_SIZE] = {0};
|
||||||
@ -300,54 +301,66 @@ RawHID::RawHID(const QString &deviceName)
|
|||||||
m_mutex(NULL)
|
m_mutex(NULL)
|
||||||
{
|
{
|
||||||
|
|
||||||
m_mutex = new QMutex(QMutex::Recursive);
|
m_mutex = new QMutex(QMutex::Recursive);
|
||||||
|
m_startedMutex = new QMutex();
|
||||||
|
|
||||||
// detect if the USB device is unplugged
|
// detect if the USB device is unplugged
|
||||||
QObject::connect(&dev, SIGNAL(deviceUnplugged(int)), this, SLOT(onDeviceUnplugged(int)));
|
QObject::connect(&dev, SIGNAL(deviceUnplugged(int)), this, SLOT(onDeviceUnplugged(int)));
|
||||||
|
|
||||||
int opened = dev.open(USB_MAX_DEVICES, USBMonitor::idVendor_OpenPilot, -1, USB_USAGE_PAGE, USB_USAGE);
|
|
||||||
for (int i =0; i< opened; i++) {
|
|
||||||
if (deviceName == dev.getserial(i))
|
|
||||||
m_deviceNo = i;
|
|
||||||
else
|
|
||||||
dev.close(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO: NOT WORKING FOR MULTIPLE DEVICES with the same PID!
|
|
||||||
QList<USBPortInfo> devices = USBMonitor::instance()->availableDevices(USBMonitor::idVendor_OpenPilot,-1,-1,USBMonitor::Running);
|
|
||||||
foreach( USBPortInfo device, devices) {
|
|
||||||
if (deviceName == device.serialNumber) {
|
|
||||||
opened = dev.open(1,device.vendorID, device.productID,USB_USAGE_PAGE,USB_USAGE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
//didn't find the device we are trying to open (shouldnt happen)
|
|
||||||
if (opened < 0)
|
|
||||||
{
|
|
||||||
qDebug() << "Error: cannot open device " << deviceName;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//m_deviceNo = 0;
|
|
||||||
m_readThread = new RawHIDReadThread(this);
|
|
||||||
m_writeThread = new RawHIDWriteThread(this);
|
m_writeThread = new RawHIDWriteThread(this);
|
||||||
|
|
||||||
|
// Starting the read thread will lock the m_startexMutex until the
|
||||||
|
// device is opened (which happens in that thread).
|
||||||
|
m_readThread = new RawHIDReadThread(this);
|
||||||
m_readThread->start();
|
m_readThread->start();
|
||||||
|
|
||||||
|
m_startedMutex->lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RawHID::openDevice This method opens the USB connection
|
||||||
|
* It is uses as a callback from the read thread so that the USB
|
||||||
|
* system code is registered in that thread instead of the calling
|
||||||
|
* thread (usually UI)
|
||||||
|
*/
|
||||||
|
bool RawHID::openDevice() {
|
||||||
|
int opened = dev.open(USB_MAX_DEVICES, USBMonitor::idVendor_OpenPilot, -1, USB_USAGE_PAGE, USB_USAGE);
|
||||||
|
for (int i =0; i< opened; i++) {
|
||||||
|
if (serialNumber == dev.getserial(i))
|
||||||
|
m_deviceNo = i;
|
||||||
|
else
|
||||||
|
dev.close(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now things are opened or not (from read thread) allow the constructor to complete
|
||||||
|
m_startedMutex->unlock();
|
||||||
|
|
||||||
|
//didn't find the device we are trying to open (shouldnt happen)
|
||||||
|
device_open = opened >= 0;
|
||||||
|
if (opened < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
m_writeThread->start();
|
m_writeThread->start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RawHID::closeDevice This method closes the USB connection
|
||||||
|
* It is uses as a callback from the read thread so that the USB
|
||||||
|
* system code is unregistered from that thread\
|
||||||
|
*/
|
||||||
|
bool RawHID::closeDevice() {
|
||||||
|
dev.close(m_deviceNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
RawHID::~RawHID()
|
RawHID::~RawHID()
|
||||||
{
|
{
|
||||||
dev.close(m_deviceNo);
|
// If the read thread exists then the device is open
|
||||||
|
if (m_readThread)
|
||||||
if (m_readThread)
|
close();
|
||||||
delete m_readThread;
|
|
||||||
|
|
||||||
if (m_writeThread)
|
|
||||||
delete m_writeThread;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RawHID::onDeviceUnplugged(int num)
|
void RawHID::onDeviceUnplugged(int num)
|
||||||
@ -356,7 +369,6 @@ void RawHID::onDeviceUnplugged(int num)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// the USB device has been unplugged
|
// the USB device has been unplugged
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,43 +381,38 @@ bool RawHID::open(OpenMode mode)
|
|||||||
|
|
||||||
QIODevice::open(mode);
|
QIODevice::open(mode);
|
||||||
|
|
||||||
if (!m_readThread)
|
Q_ASSERT(m_readThread);
|
||||||
m_readThread = new RawHIDReadThread(this);
|
Q_ASSERT(m_writeThread);
|
||||||
|
if (m_readThread) m_readThread->start();
|
||||||
if (!m_writeThread)
|
if (m_writeThread) m_writeThread->start();
|
||||||
m_writeThread = new RawHIDWriteThread(this);
|
|
||||||
|
|
||||||
if (m_readThread) m_readThread->start(); // Pip
|
|
||||||
if (m_writeThread) m_writeThread->start(); // Pip
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RawHID::close()
|
void RawHID::close()
|
||||||
{
|
{
|
||||||
emit aboutToClose();
|
qDebug() << "RawHID::close()";
|
||||||
|
emit aboutToClose();
|
||||||
|
if (m_writeThread)
|
||||||
|
{
|
||||||
|
qDebug() << "About to terminate write thread";
|
||||||
|
m_writeThread->terminate();
|
||||||
|
delete m_writeThread;
|
||||||
|
m_writeThread = NULL;
|
||||||
|
qDebug() << "Write thread terminated";
|
||||||
|
}
|
||||||
|
|
||||||
m_mutex->lock();
|
|
||||||
|
|
||||||
if (m_readThread)
|
if (m_readThread)
|
||||||
{
|
{
|
||||||
m_readThread->terminate();
|
qDebug() << "About to terminate read thread";
|
||||||
delete m_readThread; // calls wait
|
m_readThread->terminate();
|
||||||
m_readThread = NULL;
|
delete m_readThread; // calls wait
|
||||||
}
|
m_readThread = NULL;
|
||||||
|
qDebug() << "Read thread terminated";
|
||||||
|
}
|
||||||
|
|
||||||
if (m_writeThread)
|
emit closed();
|
||||||
{
|
|
||||||
m_writeThread->terminate();
|
|
||||||
delete m_writeThread;
|
|
||||||
m_writeThread = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
dev.close(m_deviceNo);
|
|
||||||
|
|
||||||
m_mutex->unlock();
|
|
||||||
|
|
||||||
emit closed();
|
|
||||||
|
|
||||||
QIODevice::close();
|
QIODevice::close();
|
||||||
}
|
}
|
||||||
|
@ -74,15 +74,23 @@ protected:
|
|||||||
virtual qint64 bytesAvailable() const;
|
virtual qint64 bytesAvailable() const;
|
||||||
virtual qint64 bytesToWrite() const;
|
virtual qint64 bytesToWrite() const;
|
||||||
|
|
||||||
|
//! Callback from the read thread to open the device
|
||||||
|
bool openDevice();
|
||||||
|
|
||||||
|
//! Callback from teh read thread to close the device
|
||||||
|
bool closeDevice();
|
||||||
|
|
||||||
QString serialNumber;
|
QString serialNumber;
|
||||||
|
|
||||||
int m_deviceNo;
|
int m_deviceNo;
|
||||||
pjrc_rawhid dev;
|
pjrc_rawhid dev;
|
||||||
|
bool device_open;
|
||||||
|
|
||||||
RawHIDReadThread *m_readThread;
|
RawHIDReadThread *m_readThread;
|
||||||
RawHIDWriteThread *m_writeThread;
|
RawHIDWriteThread *m_writeThread;
|
||||||
|
|
||||||
QMutex *m_mutex;
|
QMutex *m_mutex;
|
||||||
|
QMutex *m_startedMutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RAWHID_H
|
#endif // RAWHID_H
|
||||||
|
@ -72,6 +72,7 @@ void RawHIDConnection::onDeviceConnected()
|
|||||||
*/
|
*/
|
||||||
void RawHIDConnection::onDeviceDisconnected()
|
void RawHIDConnection::onDeviceDisconnected()
|
||||||
{
|
{
|
||||||
|
qDebug() << "onDeviceDisconnected()";
|
||||||
if (enablePolling)
|
if (enablePolling)
|
||||||
emit availableDevChanged(this);
|
emit availableDevChanged(this);
|
||||||
}
|
}
|
||||||
@ -110,15 +111,13 @@ QIODevice *RawHIDConnection::openDevice(const QString &deviceName)
|
|||||||
void RawHIDConnection::closeDevice(const QString &deviceName)
|
void RawHIDConnection::closeDevice(const QString &deviceName)
|
||||||
{
|
{
|
||||||
Q_UNUSED(deviceName);
|
Q_UNUSED(deviceName);
|
||||||
//added by andrew...
|
|
||||||
if (RawHidHandle)
|
if (RawHidHandle)
|
||||||
{
|
{
|
||||||
|
qDebug() << "Closing the device here";
|
||||||
RawHidHandle->close();
|
RawHidHandle->close();
|
||||||
|
|
||||||
delete RawHidHandle;
|
delete RawHidHandle;
|
||||||
RawHidHandle = NULL;
|
RawHidHandle = NULL;
|
||||||
}
|
}
|
||||||
//end added by andrew
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString RawHIDConnection::connectionName()
|
QString RawHIDConnection::connectionName()
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
#include "rawhid_global.h"
|
#include "rawhid_global.h"
|
||||||
#include "rawhid.h"
|
#include "rawhid.h"
|
||||||
#include "usbmonitor.h"
|
#include "usbmonitor.h"
|
||||||
#include "usbsignalfilter.h"
|
|
||||||
#include "coreplugin/iconnection.h"
|
#include "coreplugin/iconnection.h"
|
||||||
#include <extensionsystem/iplugin.h>
|
#include <extensionsystem/iplugin.h>
|
||||||
|
|
||||||
|
@ -87,11 +87,13 @@ DFUObject::DFUObject(bool _debug,bool _use_serial,QString portname):
|
|||||||
m_eventloop.exec();
|
m_eventloop.exec();
|
||||||
QList<USBPortInfo> devices;
|
QList<USBPortInfo> devices;
|
||||||
devices = USBMonitor::instance()->availableDevices(0x20a0,-1,-1,USBMonitor::Bootloader);
|
devices = USBMonitor::instance()->availableDevices(0x20a0,-1,-1,USBMonitor::Bootloader);
|
||||||
if (devices.length()==1 && hidHandle.open(1,devices.first().vendorID,devices.first().productID,0,0)==1) {
|
if (devices.length()==1) {
|
||||||
qDebug()<<"OP_DFU detected first time";
|
if (hidHandle.open(1,devices.first().vendorID,devices.first().productID,0,0)==1) {
|
||||||
mready=true;
|
mready=true;
|
||||||
QTimer::singleShot(200,&m_eventloop, SLOT(quit()));
|
QTimer::singleShot(200,&m_eventloop, SLOT(quit()));
|
||||||
m_eventloop.exec();
|
m_eventloop.exec();
|
||||||
|
} else
|
||||||
|
hidHandle.close(0);
|
||||||
} else {
|
} else {
|
||||||
// Wait for the board to appear on the USB bus:
|
// Wait for the board to appear on the USB bus:
|
||||||
USBSignalFilter filter(0x20a0,-1,-1,USBMonitor::Bootloader);
|
USBSignalFilter filter(0x20a0,-1,-1,USBMonitor::Bootloader);
|
||||||
@ -106,17 +108,20 @@ DFUObject::DFUObject(bool _debug,bool _use_serial,QString portname):
|
|||||||
QTimer::singleShot(2000,&m_eventloop, SLOT(quit()));
|
QTimer::singleShot(2000,&m_eventloop, SLOT(quit()));
|
||||||
m_eventloop.exec();
|
m_eventloop.exec();
|
||||||
devices = USBMonitor::instance()->availableDevices(0x20a0,-1,-1,USBMonitor::Bootloader);
|
devices = USBMonitor::instance()->availableDevices(0x20a0,-1,-1,USBMonitor::Bootloader);
|
||||||
|
qDebug() << "Devices length: " << devices.length();
|
||||||
if (devices.length()==1) {
|
if (devices.length()==1) {
|
||||||
if(hidHandle.open(1,devices.first().vendorID,devices.first().productID,0,0)==1)
|
qDebug() << "Opening device";
|
||||||
|
if(hidHandle.open(1,devices.first().vendorID,devices.first().productID,0,0)==1)
|
||||||
{
|
{
|
||||||
QTimer::singleShot(200,&m_eventloop, SLOT(quit()));
|
QTimer::singleShot(200,&m_eventloop, SLOT(quit()));
|
||||||
m_eventloop.exec();
|
m_eventloop.exec();
|
||||||
qDebug()<<"OP_DFU detected after delay";
|
qDebug()<<"OP_DFU detected after delay";
|
||||||
mready=true;
|
mready=true;
|
||||||
|
qDebug() << "Detected";
|
||||||
break;
|
break;
|
||||||
}
|
} else
|
||||||
}
|
hidHandle.close(0);
|
||||||
else {
|
} else {
|
||||||
qDebug() << devices.length() << " device(s) detected, don't know what to do!";
|
qDebug() << devices.length() << " device(s) detected, don't know what to do!";
|
||||||
mready = false;
|
mready = false;
|
||||||
}
|
}
|
||||||
@ -171,7 +176,6 @@ bool DFUObject::enterDFU(int const &devNumber)
|
|||||||
buf[9] = 1; //DFU Data3
|
buf[9] = 1; //DFU Data3
|
||||||
|
|
||||||
int result = sendData(buf, BUF_LEN);
|
int result = sendData(buf, BUF_LEN);
|
||||||
// int result = hidHandle.send(0,buf, BUF_LEN, 500);
|
|
||||||
if(result<1)
|
if(result<1)
|
||||||
return false;
|
return false;
|
||||||
if(debug)
|
if(debug)
|
||||||
@ -216,7 +220,6 @@ bool DFUObject::StartUpload(qint32 const & numberOfBytes, TransferTypes const &
|
|||||||
|
|
||||||
int result = sendData(buf, BUF_LEN);
|
int result = sendData(buf, BUF_LEN);
|
||||||
delay::msleep(1000);
|
delay::msleep(1000);
|
||||||
// int result = hidHandle.send(0,buf, BUF_LEN, 5000);
|
|
||||||
|
|
||||||
if(debug)
|
if(debug)
|
||||||
qDebug() << result << " bytes sent";
|
qDebug() << result << " bytes sent";
|
||||||
@ -441,7 +444,6 @@ bool DFUObject::StartDownloadT(QByteArray *fw, qint32 const & numberOfBytes, Tra
|
|||||||
buf[9] = 1; //DFU Data3
|
buf[9] = 1; //DFU Data3
|
||||||
|
|
||||||
int result = sendData(buf, BUF_LEN);
|
int result = sendData(buf, BUF_LEN);
|
||||||
//int result = hidHandle.send(0,buf, BUF_LEN, 500);
|
|
||||||
if(debug)
|
if(debug)
|
||||||
qDebug() << "StartDownload:"<<numberOfPackets<<"packets"<<" Last Packet Size="<<lastPacketCount<<" "<<result << " bytes sent";
|
qDebug() << "StartDownload:"<<numberOfPackets<<"packets"<<" Last Packet Size="<<lastPacketCount<<" "<<result << " bytes sent";
|
||||||
float percentage;
|
float percentage;
|
||||||
@ -457,7 +459,6 @@ bool DFUObject::StartDownloadT(QByteArray *fw, qint32 const & numberOfBytes, Tra
|
|||||||
laspercentage=(int)percentage;
|
laspercentage=(int)percentage;
|
||||||
|
|
||||||
result = receiveData(buf,BUF_LEN);
|
result = receiveData(buf,BUF_LEN);
|
||||||
//result = hidHandle.receive(0,buf,BUF_LEN,5000);
|
|
||||||
if(debug)
|
if(debug)
|
||||||
qDebug() << result << " bytes received"<<" Count="<<x<<"-"<<(int)buf[2]<<";"<<(int)buf[3]<<";"<<(int)buf[4]<<";"<<(int)buf[5]<<" Data="<<(int)buf[6]<<";"<<(int)buf[7]<<";"<<(int)buf[8]<<";"<<(int)buf[9];
|
qDebug() << result << " bytes received"<<" Count="<<x<<"-"<<(int)buf[2]<<";"<<(int)buf[3]<<";"<<(int)buf[4]<<";"<<(int)buf[5]<<" Data="<<(int)buf[6]<<";"<<(int)buf[7]<<";"<<(int)buf[8]<<";"<<(int)buf[9];
|
||||||
if(x==numberOfPackets-1)
|
if(x==numberOfPackets-1)
|
||||||
@ -508,7 +509,6 @@ int DFUObject::AbortOperation(void)
|
|||||||
buf[9] = 0;
|
buf[9] = 0;
|
||||||
|
|
||||||
return sendData(buf, BUF_LEN);
|
return sendData(buf, BUF_LEN);
|
||||||
//return hidHandle.send(0,buf, BUF_LEN, 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -538,7 +538,6 @@ int DFUObject::JumpToApp(bool safeboot)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return sendData(buf, BUF_LEN);
|
return sendData(buf, BUF_LEN);
|
||||||
//return hidHandle.send(0,buf, BUF_LEN, 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OP_DFU::Status DFUObject::StatusRequest()
|
OP_DFU::Status DFUObject::StatusRequest()
|
||||||
@ -556,11 +555,9 @@ OP_DFU::Status DFUObject::StatusRequest()
|
|||||||
buf[9] = 0;
|
buf[9] = 0;
|
||||||
|
|
||||||
int result = sendData(buf, BUF_LEN);
|
int result = sendData(buf, BUF_LEN);
|
||||||
//int result = hidHandle.send(0,buf, BUF_LEN, 10000);
|
|
||||||
if(debug)
|
if(debug)
|
||||||
qDebug() << "StatusRequest: " << result << " bytes sent";
|
qDebug() << "StatusRequest: " << result << " bytes sent";
|
||||||
result = receiveData(buf,BUF_LEN);
|
result = receiveData(buf,BUF_LEN);
|
||||||
// result = hidHandle.receive(0,buf,BUF_LEN,10000);
|
|
||||||
if(debug)
|
if(debug)
|
||||||
qDebug() << "StatusRequest: " << result << " bytes received";
|
qDebug() << "StatusRequest: " << result << " bytes received";
|
||||||
if(buf[1]==OP_DFU::Status_Rep)
|
if(buf[1]==OP_DFU::Status_Rep)
|
||||||
@ -590,22 +587,17 @@ bool DFUObject::findDevices()
|
|||||||
buf[9] = 0;
|
buf[9] = 0;
|
||||||
|
|
||||||
int result = sendData(buf, BUF_LEN);
|
int result = sendData(buf, BUF_LEN);
|
||||||
//int result = hidHandle.send(0,buf, BUF_LEN, 5000);
|
if (result < 1)
|
||||||
if(result<1)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
result = receiveData(buf,BUF_LEN);
|
result = receiveData(buf,BUF_LEN);
|
||||||
//result = hidHandle.receive(0,buf,BUF_LEN,5000);
|
if (result < 1)
|
||||||
if(result<1)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
numberOfDevices=buf[7];
|
numberOfDevices=buf[7];
|
||||||
RWFlags=buf[8];
|
RWFlags=buf[8];
|
||||||
RWFlags=RWFlags<<8 | buf[9];
|
RWFlags=RWFlags<<8 | buf[9];
|
||||||
|
|
||||||
|
|
||||||
if(buf[1]==OP_DFU::Rep_Capabilities)
|
if(buf[1]==OP_DFU::Rep_Capabilities)
|
||||||
{
|
{
|
||||||
for(int x=0;x<numberOfDevices;++x)
|
for(int x=0;x<numberOfDevices;++x)
|
||||||
@ -626,9 +618,6 @@ bool DFUObject::findDevices()
|
|||||||
buf[9] = 0;
|
buf[9] = 0;
|
||||||
int result = sendData(buf, BUF_LEN);
|
int result = sendData(buf, BUF_LEN);
|
||||||
result = receiveData(buf,BUF_LEN);
|
result = receiveData(buf,BUF_LEN);
|
||||||
// int result = hidHandle.send(0,buf, BUF_LEN, 5000);
|
|
||||||
// result = hidHandle.receive(0,buf,BUF_LEN,5000);
|
|
||||||
//devices[x].ID=buf[9];
|
|
||||||
devices[x].ID=buf[14];
|
devices[x].ID=buf[14];
|
||||||
devices[x].ID=devices[x].ID<<8 | (quint8)buf[15];
|
devices[x].ID=devices[x].ID<<8 | (quint8)buf[15];
|
||||||
devices[x].BL_Version=buf[7];
|
devices[x].BL_Version=buf[7];
|
||||||
@ -684,8 +673,6 @@ bool DFUObject::EndOperation()
|
|||||||
buf[9] = 0;
|
buf[9] = 0;
|
||||||
|
|
||||||
int result = sendData(buf, BUF_LEN);
|
int result = sendData(buf, BUF_LEN);
|
||||||
// int result = hidHandle.send(0,buf, BUF_LEN, 5000);
|
|
||||||
// hidHandle.receive(0,buf,BUF_LEN,5000);
|
|
||||||
if(debug)
|
if(debug)
|
||||||
qDebug() << result << " bytes sent";
|
qDebug() << result << " bytes sent";
|
||||||
if(result>0)
|
if(result>0)
|
||||||
|
@ -249,8 +249,8 @@ void UploaderGadgetWidget::goToBootloader(UAVObject* callerObj, bool success)
|
|||||||
|
|
||||||
// The board is now reset: we have to disconnect telemetry
|
// The board is now reset: we have to disconnect telemetry
|
||||||
Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
|
Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
|
||||||
QString dli = cm->getCurrentDevice().Name;
|
QString dli = cm->getCurrentDevice().getConName();
|
||||||
QString dlj = cm->getCurrentDevice().devName;
|
QString dlj = cm->getCurrentDevice().getConName();
|
||||||
cm->disconnectDevice();
|
cm->disconnectDevice();
|
||||||
QTimer::singleShot(200, &m_eventloop, SLOT(quit()));
|
QTimer::singleShot(200, &m_eventloop, SLOT(quit()));
|
||||||
m_eventloop.exec();
|
m_eventloop.exec();
|
||||||
@ -378,7 +378,8 @@ void UploaderGadgetWidget::systemSafeBoot()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Tells the system to boot (from Bootloader state)
|
* Tells the system to boot (from Bootloader state)
|
||||||
|
* @param[in] safeboot Indicates whether the firmware should use the stock HWSettings
|
||||||
*/
|
*/
|
||||||
void UploaderGadgetWidget::commonSystemBoot(bool safeboot)
|
void UploaderGadgetWidget::commonSystemBoot(bool safeboot)
|
||||||
{
|
{
|
||||||
@ -456,7 +457,7 @@ void UploaderGadgetWidget::systemRescue()
|
|||||||
delete dfu;
|
delete dfu;
|
||||||
dfu = NULL;
|
dfu = NULL;
|
||||||
}
|
}
|
||||||
// Avoid dumb users pressing Rescue twice. It can happen.
|
// Avoid users pressing Rescue twice.
|
||||||
m_config->rescueButton->setEnabled(false);
|
m_config->rescueButton->setEnabled(false);
|
||||||
|
|
||||||
// Now we're good to go:
|
// Now we're good to go:
|
||||||
@ -523,25 +524,6 @@ void UploaderGadgetWidget::systemRescue()
|
|||||||
m_config->rescueButton->setEnabled(true);
|
m_config->rescueButton->setEnabled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((eBoardCC != dfu->GetBoardType(0)) && (QMessageBox::question(this,tr("OpenPilot Uploader"),tr("If you want to search for other boards connect power now and press Yes"),QMessageBox::Yes,QMessageBox::No)==QMessageBox::Yes))
|
|
||||||
{
|
|
||||||
log("\nWaiting...");
|
|
||||||
QTimer::singleShot(3000, &m_eventloop, SLOT(quit()));
|
|
||||||
m_eventloop.exec();
|
|
||||||
log("Detecting second board...");
|
|
||||||
repaint();
|
|
||||||
if(!dfu->findDevices())
|
|
||||||
{
|
|
||||||
// We will only end up here in case somehow all the boards
|
|
||||||
// disappeared, including the one we detected earlier.
|
|
||||||
log("Could not detect any board, aborting!");
|
|
||||||
delete dfu;
|
|
||||||
dfu = NULL;
|
|
||||||
cm->resumePolling();
|
|
||||||
m_config->rescueButton->setEnabled(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log(QString("Found ") + QString::number(dfu->numberOfDevices) + QString(" device(s)."));
|
log(QString("Found ") + QString::number(dfu->numberOfDevices) + QString(" device(s)."));
|
||||||
if (dfu->numberOfDevices > 5) {
|
if (dfu->numberOfDevices > 5) {
|
||||||
log("Inconsistent number of devices, aborting!");
|
log("Inconsistent number of devices, aborting!");
|
||||||
@ -566,6 +548,7 @@ void UploaderGadgetWidget::systemRescue()
|
|||||||
m_config->rescueButton->setEnabled(false);
|
m_config->rescueButton->setEnabled(false);
|
||||||
currentStep = IAP_STATE_BOOTLOADER; // So that we can boot from the GUI afterwards.
|
currentStep = IAP_STATE_BOOTLOADER; // So that we can boot from the GUI afterwards.
|
||||||
}
|
}
|
||||||
|
|
||||||
void UploaderGadgetWidget::perform()
|
void UploaderGadgetWidget::perform()
|
||||||
{
|
{
|
||||||
if(m_progress->value()==19)
|
if(m_progress->value()==19)
|
||||||
|
Loading…
Reference in New Issue
Block a user