|
|
|
/*
|
|
|
|
* Copyright 2019-2020 PixlOne
|
|
|
|
*
|
|
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
#include <utility>
|
|
|
|
#include "../../util/thread.h"
|
|
|
|
#include "Device.h"
|
|
|
|
#include "Report.h"
|
|
|
|
#include "../hidpp20/features/Root.h"
|
|
|
|
#include "../hidpp20/features/DeviceName.h"
|
|
|
|
#include "../hidpp20/Error.h"
|
|
|
|
#include "../hidpp10/Error.h"
|
|
|
|
#include "../dj/Receiver.h"
|
|
|
|
|
|
|
|
using namespace logid::backend;
|
|
|
|
using namespace logid::backend::hidpp;
|
|
|
|
|
|
|
|
const char* Device::InvalidDevice::what() const noexcept
|
|
|
|
{
|
|
|
|
switch(_reason) {
|
|
|
|
case NoHIDPPReport:
|
|
|
|
return "Invalid HID++ device";
|
|
|
|
case InvalidRawDevice:
|
|
|
|
return "Invalid raw device";
|
|
|
|
case Asleep:
|
|
|
|
return "Device asleep";
|
|
|
|
default:
|
|
|
|
return "Invalid device";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept
|
|
|
|
{
|
|
|
|
return _reason;
|
|
|
|
}
|
|
|
|
|
|
|
|
Device::Device(const std::string& path, DeviceIndex index):
|
|
|
|
_raw_device (std::make_shared<raw::RawDevice>(path)), _receiver (nullptr),
|
|
|
|
_path (path), _index (index)
|
|
|
|
{
|
|
|
|
_init();
|
|
|
|
}
|
|
|
|
|
|
|
|
Device::Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index) :
|
|
|
|
_raw_device (std::move(raw_device)), _receiver (nullptr),
|
|
|
|
_path (_raw_device->hidrawPath()), _index (index)
|
|
|
|
{
|
|
|
|
_init();
|
|
|
|
}
|
|
|
|
|
|
|
|
Device::Device(std::shared_ptr<dj::Receiver> receiver,
|
|
|
|
hidpp::DeviceConnectionEvent event) :
|
|
|
|
_raw_device (receiver->rawDevice()), _receiver (receiver),
|
|
|
|
_path (receiver->rawDevice()->hidrawPath()), _index (event.index)
|
|
|
|
{
|
|
|
|
// Device will throw an error soon, just do it now
|
|
|
|
if(!event.linkEstablished)
|
|
|
|
throw InvalidDevice(InvalidDevice::Asleep);
|
|
|
|
|
|
|
|
if(!event.fromTimeoutCheck)
|
|
|
|
_pid = event.pid;
|
|
|
|
else
|
|
|
|
_pid = receiver->getPairingInfo(_index).pid;
|
|
|
|
_init();
|
|
|
|
}
|
|
|
|
|
|
|
|
Device::Device(std::shared_ptr<dj::Receiver> receiver,
|
|
|
|
DeviceIndex index) : _raw_device (receiver->rawDevice()),
|
|
|
|
_receiver (receiver), _path (receiver->rawDevice()->hidrawPath()),
|
|
|
|
_index (index)
|
|
|
|
{
|
|
|
|
_pid = receiver->getPairingInfo(_index).pid;
|
|
|
|
_init();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Device::devicePath() const
|
|
|
|
{
|
|
|
|
return _path;
|
|
|
|
}
|
|
|
|
|
|
|
|
DeviceIndex Device::deviceIndex() const
|
|
|
|
{
|
|
|
|
return _index;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::tuple<uint8_t, uint8_t> Device::version() const
|
|
|
|
{
|
|
|
|
return _version;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Device::_init()
|
|
|
|
{
|
|
|
|
_listening = false;
|
|
|
|
_supported_reports = getSupportedReports(_raw_device->reportDescriptor());
|
|
|
|
if(!_supported_reports)
|
|
|
|
throw InvalidDevice(InvalidDevice::NoHIDPPReport);
|
|
|
|
|
|
|
|
try {
|
|
|
|
hidpp20::EssentialRoot root(this);
|
|
|
|
_version = root.getVersion();
|
|
|
|
} catch(hidpp10::Error &e) {
|
|
|
|
// Valid HID++ 1.0 devices should send an InvalidSubID error
|
|
|
|
if(e.code() != hidpp10::Error::InvalidSubID)
|
|
|
|
throw;
|
|
|
|
|
|
|
|
// HID++ 2.0 is not supported, assume HID++ 1.0
|
|
|
|
_version = std::make_tuple(1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!_receiver) {
|
|
|
|
_pid = _raw_device->productId();
|
|
|
|
if(std::get<0>(_version) >= 2) {
|
|
|
|
try {
|
|
|
|
hidpp20::EssentialDeviceName deviceName(this);
|
|
|
|
_name = deviceName.getName();
|
|
|
|
} catch(hidpp20::UnsupportedFeature &e) {
|
|
|
|
_name = _raw_device->name();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_name = _raw_device->name();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if(std::get<0>(_version) >= 2) {
|
|
|
|
try {
|
|
|
|
hidpp20::EssentialDeviceName deviceName(this);
|
|
|
|
_name = deviceName.getName();
|
|
|
|
} catch(hidpp20::UnsupportedFeature &e) {
|
|
|
|
_name = _receiver->getDeviceName(_index);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_name = _receiver->getDeviceName(_index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Device::~Device()
|
|
|
|
{
|
|
|
|
if(_listening)
|
|
|
|
_raw_device->removeEventHandler("DEV_" + std::to_string(_index));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Device::addEventHandler(const std::string& nickname,
|
|
|
|
const std::shared_ptr<EventHandler>& handler)
|
|
|
|
{
|
|
|
|
assert(_event_handlers.find(nickname) == _event_handlers.end());
|
|
|
|
|
|
|
|
_event_handlers.emplace(nickname, handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Device::removeEventHandler(const std::string& nickname)
|
|
|
|
{
|
|
|
|
_event_handlers.erase(nickname);
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::map<std::string, std::shared_ptr<EventHandler>>&
|
|
|
|
Device::eventHandlers()
|
|
|
|
{
|
|
|
|
return _event_handlers;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Device::handleEvent(Report& report)
|
|
|
|
{
|
|
|
|
for(auto& handler : _event_handlers)
|
|
|
|
if(handler.second->condition(report))
|
|
|
|
handler.second->callback(report);
|
|
|
|
}
|
|
|
|
|
|
|
|
Report Device::sendReport(Report& report)
|
|
|
|
{
|
|
|
|
switch(report.type())
|
|
|
|
{
|
|
|
|
case Report::Type::Short:
|
|
|
|
if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
|
|
|
|
report.setType(Report::Type::Long);
|
|
|
|
break;
|
|
|
|
case Report::Type::Long:
|
|
|
|
/* Report can be truncated, but that isn't a good idea. */
|
|
|
|
assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto raw_response = _raw_device->sendReport(report.rawReport());
|
|
|
|
|
|
|
|
Report response(raw_response);
|
|
|
|
|
|
|
|
Report::Hidpp10Error hidpp10_error{};
|
|
|
|
if(response.isError10(&hidpp10_error))
|
|
|
|
throw hidpp10::Error(hidpp10_error.error_code);
|
|
|
|
|
|
|
|
Report::Hidpp20Error hidpp20_error{};
|
|
|
|
if(response.isError20(&hidpp20_error))
|
|
|
|
throw hidpp20::Error(hidpp20_error.error_code);
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Device::sendReportNoResponse(Report &report)
|
|
|
|
{
|
|
|
|
switch(report.type())
|
|
|
|
{
|
|
|
|
case Report::Type::Short:
|
|
|
|
if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
|
|
|
|
report.setType(Report::Type::Long);
|
|
|
|
break;
|
|
|
|
case Report::Type::Long:
|
|
|
|
/* Report can be truncated, but that isn't a good idea. */
|
|
|
|
assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
_raw_device->sendReportNoResponse(report.rawReport());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Device::name() const
|
|
|
|
{
|
|
|
|
return _name;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t Device::pid() const
|
|
|
|
{
|
|
|
|
return _pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Device::listen()
|
|
|
|
{
|
|
|
|
if(!_raw_device->isListening())
|
|
|
|
_raw_device->listenAsync();
|
|
|
|
|
|
|
|
// Pass all HID++ events with device index to this device.
|
|
|
|
auto handler = std::make_shared<raw::RawEventHandler>();
|
|
|
|
handler->condition = [index=this->_index](std::vector<uint8_t>& report)
|
|
|
|
->bool {
|
|
|
|
return (report[Offset::Type] == Report::Type::Short ||
|
|
|
|
report[Offset::Type] == Report::Type::Long) &&
|
|
|
|
(report[Offset::DeviceIndex] == index);
|
|
|
|
};
|
|
|
|
handler->callback = [this](std::vector<uint8_t>& report)->void {
|
|
|
|
Report _report(report);
|
|
|
|
this->handleEvent(_report);
|
|
|
|
};
|
|
|
|
|
|
|
|
_raw_device->addEventHandler("DEV_" + std::to_string(_index), handler);
|
|
|
|
_listening = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Device::stopListening()
|
|
|
|
{
|
|
|
|
if(_listening)
|
|
|
|
_raw_device->removeEventHandler("DEV_" + std::to_string(_index));
|
|
|
|
|
|
|
|
_listening = false;
|
|
|
|
|
|
|
|
if(!_raw_device->eventHandlers().empty())
|
|
|
|
_raw_device->stopListener();
|
|
|
|
}
|