From 9a47c2ed4ca11af5d851605e0da6218fbdab0586 Mon Sep 17 00:00:00 2001 From: dslul Date: Thu, 19 Oct 2017 16:43:52 +0200 Subject: [PATCH] First commit --- .gitignore | 1 + 99-hidraw-logitech-g402.rules | 1 + README.md | 6 + hidpp/hidpp.pro | 68 ++ hidpp/hidpp/Device.cpp | 248 ++++++ hidpp/hidpp/Device.h | 111 +++ hidpp/hidpp/DeviceInfo.cpp | 109 +++ hidpp/hidpp/DeviceInfo.h | 41 + hidpp/hidpp/Report.cpp | 275 +++++++ hidpp/hidpp/Report.h | 281 +++++++ hidpp/hidpp/defs.h | 62 ++ hidpp/hidpp/ids.h | 40 + hidpp/hidpp10/Address.cpp | 28 + hidpp/hidpp10/Address.h | 37 + hidpp/hidpp10/Device.cpp | 195 +++++ hidpp/hidpp10/Device.h | 52 ++ hidpp/hidpp10/DeviceInfo.h | 53 ++ hidpp/hidpp10/Error.cpp | 66 ++ hidpp/hidpp10/Error.h | 59 ++ hidpp/hidpp10/IIndividualFeatures.cpp | 67 ++ hidpp/hidpp10/IIndividualFeatures.h | 58 ++ hidpp/hidpp10/IMemory.cpp | 139 ++++ hidpp/hidpp10/IMemory.h | 58 ++ hidpp/hidpp10/IProfile.cpp | 72 ++ hidpp/hidpp10/IProfile.h | 62 ++ hidpp/hidpp10/IReceiver.cpp | 106 +++ hidpp/hidpp10/IReceiver.h | 84 ++ hidpp/hidpp10/IResolution.cpp | 89 +++ hidpp/hidpp10/IResolution.h | 64 ++ hidpp/hidpp10/Macro.cpp | 969 +++++++++++++++++++++++ hidpp/hidpp10/Macro.h | 163 ++++ hidpp/hidpp10/MemoryMapping.cpp | 76 ++ hidpp/hidpp10/MemoryMapping.h | 63 ++ hidpp/hidpp10/Profile.cpp | 432 ++++++++++ hidpp/hidpp10/Profile.h | 182 +++++ hidpp/hidpp10/ProfileDirectory.cpp | 54 ++ hidpp/hidpp10/ProfileDirectory.h | 49 ++ hidpp/hidpp10/Sensor.cpp | 138 ++++ hidpp/hidpp10/Sensor.h | 82 ++ hidpp/hidpp10/WriteError.cpp | 37 + hidpp/hidpp10/WriteError.h | 42 + hidpp/hidpp10/defs.h | 66 ++ hidpp/hidpp20/Device.cpp | 91 +++ hidpp/hidpp20/Device.h | 39 + hidpp/hidpp20/Error.cpp | 60 ++ hidpp/hidpp20/Error.h | 56 ++ hidpp/hidpp20/IFeatureSet.cpp | 60 ++ hidpp/hidpp20/IFeatureSet.h | 56 ++ hidpp/hidpp20/IOnboardProfiles.cpp | 126 +++ hidpp/hidpp20/IOnboardProfiles.h | 109 +++ hidpp/hidpp20/IRoot.cpp | 45 ++ hidpp/hidpp20/IRoot.h | 52 ++ hidpp/hidpp20/defs.h | 27 + hidpp/misc/CRC.cpp | 40 + hidpp/misc/CRC.h | 35 + hidpp/misc/Endian.h | 90 +++ hidpp/misc/HIDRaw.cpp | 141 ++++ hidpp/misc/HIDRaw.h | 55 ++ hidpp/misc/Log.cpp | 73 ++ hidpp/misc/Log.h | 78 ++ hidpp/misc/UsageStrings.cpp | 534 +++++++++++++ hidpp/misc/UsageStrings.h | 36 + logiconf-gui/DpiEdit.ui.qml | 29 + logiconf-gui/Page1.qml | 51 ++ logiconf-gui/Page1Form.ui.qml | 82 ++ logiconf-gui/Page2.qml | 41 + logiconf-gui/Page2Form.ui.qml | 110 +++ logiconf-gui/ProfileManager.cpp | 6 + logiconf-gui/ProfileManager.h | 17 + logiconf-gui/devicecommunicator.cpp | 158 ++++ logiconf-gui/devicecommunicator.h | 39 + logiconf-gui/logiconf-gui.pro | 42 + logiconf-gui/main.cpp | 21 + logiconf-gui/main.qml | 53 ++ logiconf-gui/qml.qrc | 11 + logiconf-gui/qtquickcontrols2.conf | 15 + logiconf.pro | 6 + other/dumps/dpi_levels_4 | 209 +++++ other/dumps/dpi_levels_5 | 138 ++++ other/dumps/fusion_engine.pcapng | Bin 0 -> 327544 bytes other/dumps/g402_commands.txt | 72 ++ other/dumps/internet_start_dump.pcapng | Bin 0 -> 43376 bytes other/dumps/lightoff | 502 ++++++++++++ other/dumps/lighton | 503 ++++++++++++ other/dumps/lights3.pcapng | Bin 0 -> 359668 bytes other/dumps/memorydump.txt | 84 ++ other/dumps/memorydump250rate.txt | 85 ++ other/dumps/page0_flash.bin | Bin 0 -> 1024 bytes other/dumps/page0_rom.bin | Bin 0 -> 1024 bytes other/dumps/page1_flash.bin | Bin 0 -> 1024 bytes other/dumps/page1_rom.bin | Bin 0 -> 1024 bytes other/dumps/page2_flash.bin | 1 + other/dumps/polling1000 | 67 ++ other/dumps/polling500 | 67 ++ other/dumps/profile_section_start.pcapng | Bin 0 -> 19012 bytes other/dumps/restore_defaults_profile.txt | 69 ++ other/dumps/settings.json | 239 ++++++ other/dumps/start | 98 +++ 98 files changed, 9503 insertions(+) create mode 100644 .gitignore create mode 100644 99-hidraw-logitech-g402.rules create mode 100644 README.md create mode 100755 hidpp/hidpp.pro create mode 100755 hidpp/hidpp/Device.cpp create mode 100755 hidpp/hidpp/Device.h create mode 100755 hidpp/hidpp/DeviceInfo.cpp create mode 100755 hidpp/hidpp/DeviceInfo.h create mode 100755 hidpp/hidpp/Report.cpp create mode 100755 hidpp/hidpp/Report.h create mode 100755 hidpp/hidpp/defs.h create mode 100755 hidpp/hidpp/ids.h create mode 100755 hidpp/hidpp10/Address.cpp create mode 100755 hidpp/hidpp10/Address.h create mode 100755 hidpp/hidpp10/Device.cpp create mode 100755 hidpp/hidpp10/Device.h create mode 100755 hidpp/hidpp10/DeviceInfo.h create mode 100755 hidpp/hidpp10/Error.cpp create mode 100755 hidpp/hidpp10/Error.h create mode 100755 hidpp/hidpp10/IIndividualFeatures.cpp create mode 100755 hidpp/hidpp10/IIndividualFeatures.h create mode 100755 hidpp/hidpp10/IMemory.cpp create mode 100755 hidpp/hidpp10/IMemory.h create mode 100755 hidpp/hidpp10/IProfile.cpp create mode 100755 hidpp/hidpp10/IProfile.h create mode 100755 hidpp/hidpp10/IReceiver.cpp create mode 100755 hidpp/hidpp10/IReceiver.h create mode 100755 hidpp/hidpp10/IResolution.cpp create mode 100755 hidpp/hidpp10/IResolution.h create mode 100755 hidpp/hidpp10/Macro.cpp create mode 100755 hidpp/hidpp10/Macro.h create mode 100755 hidpp/hidpp10/MemoryMapping.cpp create mode 100755 hidpp/hidpp10/MemoryMapping.h create mode 100755 hidpp/hidpp10/Profile.cpp create mode 100755 hidpp/hidpp10/Profile.h create mode 100755 hidpp/hidpp10/ProfileDirectory.cpp create mode 100755 hidpp/hidpp10/ProfileDirectory.h create mode 100755 hidpp/hidpp10/Sensor.cpp create mode 100755 hidpp/hidpp10/Sensor.h create mode 100755 hidpp/hidpp10/WriteError.cpp create mode 100755 hidpp/hidpp10/WriteError.h create mode 100755 hidpp/hidpp10/defs.h create mode 100755 hidpp/hidpp20/Device.cpp create mode 100755 hidpp/hidpp20/Device.h create mode 100755 hidpp/hidpp20/Error.cpp create mode 100755 hidpp/hidpp20/Error.h create mode 100755 hidpp/hidpp20/IFeatureSet.cpp create mode 100755 hidpp/hidpp20/IFeatureSet.h create mode 100755 hidpp/hidpp20/IOnboardProfiles.cpp create mode 100755 hidpp/hidpp20/IOnboardProfiles.h create mode 100755 hidpp/hidpp20/IRoot.cpp create mode 100755 hidpp/hidpp20/IRoot.h create mode 100755 hidpp/hidpp20/defs.h create mode 100755 hidpp/misc/CRC.cpp create mode 100755 hidpp/misc/CRC.h create mode 100755 hidpp/misc/Endian.h create mode 100755 hidpp/misc/HIDRaw.cpp create mode 100755 hidpp/misc/HIDRaw.h create mode 100755 hidpp/misc/Log.cpp create mode 100755 hidpp/misc/Log.h create mode 100755 hidpp/misc/UsageStrings.cpp create mode 100755 hidpp/misc/UsageStrings.h create mode 100755 logiconf-gui/DpiEdit.ui.qml create mode 100755 logiconf-gui/Page1.qml create mode 100755 logiconf-gui/Page1Form.ui.qml create mode 100755 logiconf-gui/Page2.qml create mode 100755 logiconf-gui/Page2Form.ui.qml create mode 100755 logiconf-gui/ProfileManager.cpp create mode 100755 logiconf-gui/ProfileManager.h create mode 100755 logiconf-gui/devicecommunicator.cpp create mode 100755 logiconf-gui/devicecommunicator.h create mode 100755 logiconf-gui/logiconf-gui.pro create mode 100755 logiconf-gui/main.cpp create mode 100755 logiconf-gui/main.qml create mode 100755 logiconf-gui/qml.qrc create mode 100755 logiconf-gui/qtquickcontrols2.conf create mode 100755 logiconf.pro create mode 100755 other/dumps/dpi_levels_4 create mode 100755 other/dumps/dpi_levels_5 create mode 100755 other/dumps/fusion_engine.pcapng create mode 100755 other/dumps/g402_commands.txt create mode 100755 other/dumps/internet_start_dump.pcapng create mode 100755 other/dumps/lightoff create mode 100755 other/dumps/lighton create mode 100755 other/dumps/lights3.pcapng create mode 100755 other/dumps/memorydump.txt create mode 100755 other/dumps/memorydump250rate.txt create mode 100755 other/dumps/page0_flash.bin create mode 100755 other/dumps/page0_rom.bin create mode 100755 other/dumps/page1_flash.bin create mode 100755 other/dumps/page1_rom.bin create mode 100755 other/dumps/page2_flash.bin create mode 100755 other/dumps/polling1000 create mode 100755 other/dumps/polling500 create mode 100755 other/dumps/profile_section_start.pcapng create mode 100755 other/dumps/restore_defaults_profile.txt create mode 100755 other/dumps/settings.json create mode 100755 other/dumps/start diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8af2d15 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logiconf.pro.user* diff --git a/99-hidraw-logitech-g402.rules b/99-hidraw-logitech-g402.rules new file mode 100644 index 0000000..79f0818 --- /dev/null +++ b/99-hidraw-logitech-g402.rules @@ -0,0 +1 @@ +KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c07e", MODE="0666" diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b2697d --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +Linux alternative for the Logitech Gaming Software. + +Currently, it works only for the G402 Gaming Mouse. + +INSTRUCTIONS: +move 99-hidraw-logitech-g402.rules to /lib/udev/rules.d/99-hidraw-permissions.rules, unplug and plug the mouse diff --git a/hidpp/hidpp.pro b/hidpp/hidpp.pro new file mode 100755 index 0000000..a5c060a --- /dev/null +++ b/hidpp/hidpp.pro @@ -0,0 +1,68 @@ +TARGET = hidpp +TEMPLATE = lib +CONFIG += c++11 staticlib object_parallel_to_source + +SOURCES += misc/HIDRaw.cpp \ + misc/Log.cpp \ + misc/CRC.cpp \ + misc/UsageStrings.cpp \ + hidpp/Device.cpp \ + hidpp/Report.cpp \ + hidpp/DeviceInfo.cpp \ + hidpp10/Device.cpp \ + hidpp10/Address.cpp \ + hidpp10/Error.cpp \ + hidpp10/WriteError.cpp \ + hidpp10/IMemory.cpp \ + hidpp10/IReceiver.cpp \ + hidpp10/IIndividualFeatures.cpp \ + hidpp10/Sensor.cpp \ + hidpp10/IResolution.cpp \ + hidpp10/IProfile.cpp \ + hidpp10/MemoryMapping.cpp \ + hidpp10/ProfileDirectory.cpp \ + hidpp10/Profile.cpp \ + hidpp10/Macro.cpp \ + hidpp20/Device.cpp \ + hidpp20/Error.cpp \ + hidpp20/IRoot.cpp \ + hidpp20/IFeatureSet.cpp \ + hidpp20/IOnboardProfiles.cpp + +HEADERS += hidpp/defs.h \ + hidpp/Device.h \ + hidpp/DeviceInfo.h \ + hidpp/ids.h \ + hidpp/Report.h \ + hidpp10/Address.h \ + hidpp10/defs.h \ + hidpp10/Device.h \ + hidpp10/DeviceInfo.h \ + hidpp10/Error.h \ + hidpp10/IIndividualFeatures.h \ + hidpp10/IMemory.h \ + hidpp10/IProfile.h \ + hidpp10/IReceiver.h \ + hidpp10/IResolution.h \ + hidpp10/Macro.h \ + hidpp10/MemoryMapping.h \ + hidpp10/ProfileDirectory.h \ + hidpp10/Profile.h \ + hidpp10/Sensor.h \ + hidpp10/WriteError.h \ + hidpp20/defs.h \ + hidpp20/Device.h \ + hidpp20/Error.h \ + hidpp20/IFeatureSet.h \ + hidpp20/IOnboardProfiles.h \ + hidpp20/IRoot.h \ + misc/CRC.h \ + misc/Endian.h \ + misc/HIDRaw.h \ + misc/Log.h \ + misc/UsageStrings.h + +unix { + target.path = /usr/lib + INSTALLS += target +} diff --git a/hidpp/hidpp/Device.cpp b/hidpp/hidpp/Device.cpp new file mode 100755 index 0000000..b4349fc --- /dev/null +++ b/hidpp/hidpp/Device.cpp @@ -0,0 +1,248 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace HIDPP; + +Device::NoHIDPPReportException::NoHIDPPReportException () +{ +} + +const char *Device::NoHIDPPReportException::what () const noexcept +{ + return "No HID++ report"; +} + +static const std::array ShortReportDesc = { + 0x06, 0x00, 0xFF, // Usage Page (FF00 - Vendor) + 0x09, 0x01, // Usage (0001 - Vendor) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x10, // Report ID (16) + 0x75, 0x08, // Report Size (8) + 0x95, 0x06, // Report Count (6) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x01, // Usage (0001 - Vendor) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x01, // Usage (0001 - Vendor) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection +}; + +static const std::array LongReportDesc = { + 0x06, 0x00, 0xFF, // Usage Page (FF00 - Vendor) + 0x09, 0x02, // Usage (0002 - Vendor) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x11, // Report ID (17) + 0x75, 0x08, // Report Size (8) + 0x95, 0x13, // Report Count (19) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x02, // Usage (0002 - Vendor) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x02, // Usage (0002 - Vendor) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection +}; + +/* Alternative versions from the G602 */ +static const std::array ShortReportDesc2 = { + 0x06, 0x00, 0xFF, // Usage Page (FF00 - Vendor) + 0x09, 0x01, // Usage (0001 - Vendor) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x10, // Report ID (16) + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x01, // Usage (0001 - Vendor) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x01, // Usage (0001 - Vendor) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection +}; + +static const std::array LongReportDesc2 = { + 0x06, 0x00, 0xFF, // Usage Page (FF00 - Vendor) + 0x09, 0x02, // Usage (0002 - Vendor) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x11, // Report ID (17) + 0x95, 0x13, // Report Count (19) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x02, // Usage (0002 - Vendor) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x02, // Usage (0002 - Vendor) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection +}; + +Device::Device (const std::string &path, DeviceIndex device_index): + HIDRaw (path), _device_index (device_index) +{ + const HIDRaw::ReportDescriptor &rdesc = getReportDescriptor (); + if (rdesc.end () == std::search (rdesc.begin (), rdesc.end (), + ShortReportDesc.begin (), + ShortReportDesc.end ()) && + rdesc.end () == std::search (rdesc.begin (), rdesc.end (), + ShortReportDesc2.begin (), + ShortReportDesc2.end ())) + throw NoHIDPPReportException (); + if (rdesc.end () == std::search (rdesc.begin (), rdesc.end (), + LongReportDesc.begin (), + LongReportDesc.end ()) && + rdesc.end () == std::search (rdesc.begin (), rdesc.end (), + LongReportDesc2.begin (), + LongReportDesc2.end ())) + throw NoHIDPPReportException (); + + if (device_index >= WirelessDevice1 && device_index <= WirelessDevice6) { + HIDPP10::Device ur (path, DefaultDevice); + HIDPP10::IReceiver ireceiver (&ur); + ireceiver.getDeviceInformation (device_index - 1, + nullptr, + nullptr, + &_product_id, + nullptr); + _name = ireceiver.getDeviceName (device_index - 1); + } + else { + _product_id = HIDRaw::productID (); + _name = HIDRaw::name (); + } +} + +DeviceIndex Device::deviceIndex () const +{ + return _device_index; +} + +void Device::getProtocolVersion (unsigned int &major, unsigned int &minor) +{ + constexpr int software_id = 1; // Must be a 4 bit unsigned value + Report request (Report::Short, + _device_index, + HIDPP20::IRoot::index, + HIDPP20::IRoot::Ping, + software_id); + sendReport (request); + while (true) { + Report response = getReport (true); // Time out if there is no valid response received fast enough + + if (response.deviceIndex () != _device_index) { + Log::debug () << __FUNCTION__ << ": " + << "Ignored report with wrong device index" + << std::endl; + continue; + } + + uint8_t sub_id, address, error_code; + if (response.checkErrorMessage10 (&sub_id, &address, &error_code)) { + if (sub_id != HIDPP20::IRoot::index || + address != (HIDPP20::IRoot::Ping << 4 | software_id)) { + Log::debug () << __FUNCTION__ << ": " + << "Ignored error message with wrong subID or address" + << std::endl; + continue; + } + if (error_code != HIDPP10::Error::InvalidSubID) + throw HIDPP10::Error (static_cast (error_code)); + major = 1; + minor = 0; + return; + } + uint8_t feature_index; + unsigned int function, sw_id; + if (response.checkErrorMessage20 (&feature_index, &function, &sw_id, &error_code)) { + if (feature_index != HIDPP20::IRoot::index || function != HIDPP20::IRoot::Ping || sw_id != software_id) { + Log::debug () << __FUNCTION__ << ": " + << "Ignored error message with wrong feature/function/softwareID" + << std::endl; + continue; + } + throw HIDPP20::Error (static_cast (error_code)); + } + if (response.featureIndex () == HIDPP20::IRoot::index && + response.function () == HIDPP20::IRoot::Ping && + response.softwareID () == software_id) { + major = response.params ()[0]; + minor = response.params ()[1]; + return; + } + Log::debug () << __FUNCTION__ << ": " + << "Ignored report with wrong feature/function/softwareID" + << std::endl; + } +} + +uint16_t Device::productID () const +{ + return _product_id; +} + +std::string Device::name () const +{ + return _name; +} + +int Device::sendReport (const Report &report) +{ + return writeReport (report.rawReport ()); +} + +Report Device::getReport (bool timeout) +{ + std::vector raw_report; + while (true) { + try { + raw_report.resize (Report::MaxDataLength+1); + if (timeout) + readReport (raw_report, 1); + else + readReport (raw_report); + return Report (raw_report[0], &raw_report[1], raw_report.size () - 1); + } + catch (Report::InvalidReportID e) { + // Ignore non-HID++ reports + Log::debug () << __FUNCTION__ << ": " + << "Ignored non HID++ report" << std::endl; + Log::printBytes (Log::Debug, "Ignored report:", + raw_report.begin (), raw_report.end ()); + continue; + } + catch (Report::InvalidReportLength e) { + // Ignore non-HID++ reports + Log::warning () << __FUNCTION__ << ": " + << "Invalid HID++ report length" << std::endl; + Log::printBytes (Log::Warning, "Ignored report:", + raw_report.begin (), raw_report.end ()); + continue; + } + } +} diff --git a/hidpp/hidpp/Device.h b/hidpp/hidpp/Device.h new file mode 100755 index 0000000..01e27c8 --- /dev/null +++ b/hidpp/hidpp/Device.h @@ -0,0 +1,111 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP_DEVICE_H +#define HIDPP_DEVICE_H + +#include +#include +#include + +namespace HIDPP +{ + +/** + * A generic HID++ Device + * + * \ingroup hidpp + */ +class Device: public HIDRaw +{ +public: + /** + * Exception when no HID++ report is found in the report descriptor. + */ + class NoHIDPPReportException: public std::exception + { + public: + NoHIDPPReportException (); + virtual const char *what () const noexcept; + }; + + /** + * HID++ device constructor. + * + * Open the hidraw device node at \p path. + * + * For receivers and wireless devices, multiple devices use the same hidraw + * node, \p device_index is needed to select a particular device. + * + * \throws SysCallError + * \throws NoHIDPPReportException + * \throws HIDPP10::Error Only for wireless devices, if there is an error + * while reading device information. + */ + Device (const std::string &path, DeviceIndex device_index = DefaultDevice); + + /** + * Access the device index. + */ + DeviceIndex deviceIndex () const; + + /** + * Check the HID++ protocol version. + * + * \param major Major number of the protocol version. + * \param minor Minor number of the protocol version. + */ + void getProtocolVersion (unsigned int &major, unsigned int &minor); + + /** + * Get the product ID of the device. + * + * - Use HID product ID for wired device or receivers. + * - Use wireless PID given by the receiver for wireless devices. + */ + uint16_t productID () const; + /** + * Get the product name of the device. + * + * - Use HID product name for wired device or receivers. + * - Use the name given by the receiver for wireless devices. + */ + std::string name () const; + + /** + * Send a HID++ report to this device. + */ + int sendReport (const Report &report); + /** + * Read a HID++ report from this device. + * + * It discards any non-HID++ report. + * + * \param timeout If true, try to read a report with a time out, throw HIDRaw::TimeoutError if no valid report is read fast enough. + */ + Report getReport (bool timeout = false); + +private: + DeviceIndex _device_index; + uint16_t _product_id; + std::string _name; +}; + +} + +#endif diff --git a/hidpp/hidpp/DeviceInfo.cpp b/hidpp/hidpp/DeviceInfo.cpp new file mode 100755 index 0000000..f27d91a --- /dev/null +++ b/hidpp/hidpp/DeviceInfo.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include + +using namespace HIDPP; + +DeviceInfo ReceiverInfo ( + DeviceInfo::Receiver +); + +HIDPP10::MouseInfo G5Info ( + &HIDPP10::ListSensor::S6006, + HIDPP10::IResolutionType0, + HIDPP10::NoProfile +); + +HIDPP10::MouseInfo G9Info = { + &HIDPP10::ListSensor::S6090, + HIDPP10::IResolutionType0, + HIDPP10::G9ProfileType +}; + +HIDPP10::MouseInfo G9xInfo = { + &HIDPP10::RangeSensor::S9500, + HIDPP10::IResolutionType3, + HIDPP10::G500ProfileType +}; + +HIDPP10::MouseInfo G500Info = { + &HIDPP10::RangeSensor::S9500, + HIDPP10::IResolutionType3, + HIDPP10::G500ProfileType +}; + +HIDPP10::MouseInfo G500sInfo = { + &HIDPP10::RangeSensor::S9808, + HIDPP10::IResolutionType3, + HIDPP10::G500ProfileType +}; + +HIDPP10::MouseInfo G700Info = { + &HIDPP10::RangeSensor::S9500, + HIDPP10::IResolutionType3, + HIDPP10::G700ProfileType +}; + +HIDPP10::MouseInfo G700sInfo = { + &HIDPP10::RangeSensor::S9808, + HIDPP10::IResolutionType3, + HIDPP10::G700ProfileType +}; + +const DeviceInfo *HIDPP::getDeviceInfo (uint16_t product_id) +{ + switch (product_id) { + case 0xc52b: // Unifying receiver + case 0xc52f: // Nano receiver advanced + case 0xc531: // G700 receiver + case 0xc532: // Unifying receiver + case 0xc537: // G602 receiver + return &ReceiverInfo; + + case ID::G5: + case ID::G5_2007: + case ID::G7: + return &G5Info; + + case ID::G9: + return &G9Info; + + case ID::G9x: + case ID::G9x_MW3: + return &G9xInfo; + + case ID::G500: + return &G500Info; + + case ID::G500s: + return &G500sInfo; + + case ID::G700: + return &G700Info; + + case ID::G700s: + return &G700sInfo; + + default: + return nullptr; + } +} diff --git a/hidpp/hidpp/DeviceInfo.h b/hidpp/hidpp/DeviceInfo.h new file mode 100755 index 0000000..7b78bdf --- /dev/null +++ b/hidpp/hidpp/DeviceInfo.h @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef DEVICE_INFO_H +#define DEVICE_INFO_H + +#include + +namespace HIDPP +{ + struct DeviceInfo + { + enum Type { + Receiver, + Device, + } type; + + DeviceInfo (Type type): type (type) {} + virtual ~DeviceInfo () {} // Make the structure polymorphic + }; + + const DeviceInfo *getDeviceInfo (uint16_t product_id); +} + +#endif + diff --git a/hidpp/hidpp/Report.cpp b/hidpp/hidpp/Report.cpp new file mode 100755 index 0000000..3f60f62 --- /dev/null +++ b/hidpp/hidpp/Report.cpp @@ -0,0 +1,275 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include + +using namespace HIDPP; + +Report::InvalidReportID::InvalidReportID () +{ +} + +const char *Report::InvalidReportID::what () const noexcept +{ + return "Invalid Report ID for a HID++ report"; +} + +Report::InvalidReportLength::InvalidReportLength () +{ +} + +const char *Report::InvalidReportLength::what () const noexcept +{ + return "Invalid Report Length for a HID++ report"; +} + +// Offsets +#define TYPE 0 +#define DEVICE_INDEX 1 +#define SUB_ID 2 +#define ADDRESS 3 + +Report::Report (uint8_t type, const uint8_t *data, std::size_t length): + _header ({ type }) +{ + switch (static_cast (type)) { + case Short: + _params.resize (ShortParamLength); + break; + case Long: + _params.resize (LongParamLength); + break; + default: + throw InvalidReportID (); + } + if (length != Report::HeaderLength-1+_params.size ()) + throw InvalidReportLength (); + + std::copy (data, data+HeaderLength-1, &_header[1]); + std::copy (data+HeaderLength-1, data+length, _params.begin ()); +} + +Report::Report (Type type, + HIDPP::DeviceIndex device_index, + uint8_t sub_id, + uint8_t address): + _header({ type, device_index, sub_id, address }) +{ + switch (type) { + case Short: + _params.resize (ShortParamLength, 0); + break; + case Long: + _params.resize (LongParamLength, 0); + break; + } +} + +Report::Report (HIDPP::DeviceIndex device_index, + uint8_t sub_id, + uint8_t address, + const std::vector ¶ms): + _header({ 0, device_index, sub_id, address }), + _params (params) +{ + switch (params.size ()) { + case ShortParamLength: + _header[TYPE] = Short; + break; + case LongParamLength: + _header[TYPE] = Long; + break; + default: + throw InvalidReportLength (); + } +} + +Report::Report (Type type, + DeviceIndex device_index, + uint8_t feature_index, + unsigned int function, + unsigned int sw_id): + _header({ type, device_index, feature_index, + static_cast ((function & 0x0F) << 4 | (sw_id & 0x0F)) }) +{ + switch (type) { + case Short: + _params.resize (ShortParamLength, 0); + break; + case Long: + _params.resize (LongParamLength, 0); + break; + } +} + +Report::Report (DeviceIndex device_index, + uint8_t feature_index, + unsigned int function, + unsigned int sw_id, + const std::vector ¶ms): + _header({ 0, device_index, feature_index, + static_cast ((function & 0x0F) << 4 | (sw_id & 0x0F)) }), + _params (params) +{ + switch (params.size ()) { + case ShortParamLength: + _header[TYPE] = Short; + break; + case LongParamLength: + _header[TYPE] = Long; + break; + default: + throw InvalidReportLength (); + } +} + +Report::Type Report::type () const +{ + return static_cast (_header[TYPE]); +} + +DeviceIndex Report::deviceIndex () const +{ + return static_cast (_header[DEVICE_INDEX]); +} + +uint8_t Report::subID () const +{ + return _header[SUB_ID]; +} + +void Report::setSubID (uint8_t sub_id) +{ + _header[SUB_ID] = sub_id; +} + +uint8_t Report::address () const +{ + return _header[ADDRESS]; +} + +void Report::setAddress (uint8_t address) +{ + _header[ADDRESS] = address; +} + +uint8_t Report::featureIndex () const +{ + return _header[SUB_ID]; +} + +void Report::setfeatureIndex (uint8_t feature_index) +{ + _header[SUB_ID] = feature_index; +} + +unsigned int Report::function () const +{ + return (_header[ADDRESS] & 0xF0) >> 4; +} + +void Report::setFunction (unsigned int function) +{ + _header[ADDRESS] = (function & 0x0F) << 4 | (_header[ADDRESS] & 0x0F); +} + +unsigned int Report::softwareID () const +{ + return _header[ADDRESS] & 0x0F; +} + +void Report::setSoftwareID (unsigned int sw_id) +{ + _header[ADDRESS] = (_header[ADDRESS] & 0xF0) | (sw_id & 0x0F); +} + +std::size_t Report::paramLength () const +{ + return paramLength (static_cast (_header[TYPE])); +} + +std::size_t Report::paramLength (Type type) +{ + switch (type) { + case Short: + return ShortParamLength; + case Long: + return LongParamLength; + default: + throw std::logic_error ("Invalid type"); + } +} + +std::vector &Report::params () +{ + return _params; +} + +const std::vector &Report::params () const +{ + return _params; +} + +const std::vector Report::rawReport () const +{ + std::vector report (HeaderLength + _params.size ()); + std::copy (_header.begin (), _header.end (), report.begin ()); + std::copy (_params.begin (), _params.end (), report.begin () + HeaderLength); + return report; +} + +bool Report::checkErrorMessage10 (uint8_t *sub_id, + uint8_t *address, + uint8_t *error_code) const +{ + if (static_cast (_header[TYPE]) != Short || + _header[SUB_ID] != HIDPP10::ErrorMessage) + return false; + + if (sub_id) + *sub_id = _header[3]; + if (address) + *address = _params[0]; + if (error_code) + *error_code = _params[1]; + return true; +} + +bool Report::checkErrorMessage20 (uint8_t *feature_index, + unsigned int *function, + unsigned int *sw_id, + uint8_t *error_code) const +{ + if (static_cast (_header[TYPE]) != Long || + _header[SUB_ID] != HIDPP20::ErrorMessage) + return false; + + if (feature_index) + *feature_index = _header[3]; + if (function) + *function = (_params[0] & 0xF0) >> 4; + if (sw_id) + *sw_id = _params[0] & 0x0F; + if (error_code) + *error_code = _params[1]; + return true; +} + diff --git a/hidpp/hidpp/Report.h b/hidpp/hidpp/Report.h new file mode 100755 index 0000000..1180625 --- /dev/null +++ b/hidpp/hidpp/Report.h @@ -0,0 +1,281 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP_REPORT_H +#define HIDPP_REPORT_H + +#include + +#include +#include + +namespace HIDPP +{ + +/** + * Contains a HID++ report. + * + * \ingroup hidpp + * + * It can be used for both HID++ 1.0 or 2.0 reports. + * + * Common fields are: + * - Report type (see \ref Type) + * - Device index (see \ref DeviceIndex) + * - Parameters + * + * HID++ 1.0 fields are: + * - SubID + * - Address + * + * HID++ 2.0 fields are: + * - Feature index + * - Function index + * - Software ID + */ +class Report +{ +public: + /** + * The type of the report (or report ID). + * + * The only difference between report types is the length of its + * parameters. + * + * \sa ShortParamLength LongParamLength + */ + enum Type: uint8_t { + Short = 0x10, ///< Short reports use 3 byte parameters. + Long = 0x11, ///< Long report use 16 byte parameters. + }; + + /** + * Exception for reports with invalid report ID. + */ + class InvalidReportID: std::exception + { + public: + InvalidReportID (); + virtual const char *what () const noexcept; + }; + /** + * Exception for reports where the length is not the expected one + * from the report type. + */ + class InvalidReportLength: std::exception + { + public: + InvalidReportLength (); + virtual const char *what () const noexcept; + }; + + /** + * Maximum length of a HID++ report. + */ + static constexpr std::size_t MaxDataLength = 19; + + /** + * Raw data constructor + * + * \param report_id Report ID + * \param data Raw report data + * \param length Length of the \p data array. + * + * \throws InvalidReportID + * \throws InvalidReportLength + */ + Report (uint8_t report_id, const uint8_t *data, std::size_t length); + + /** + * Access report type. + */ + Type type () const; + /** + * Access report device index. + */ + DeviceIndex deviceIndex () const; + + /** + * \name HID++ 1.0 + * + * @{ + */ + + /** + * HID++ 1.0 constructor from type. + * + * Parameters size is set from \p type and is filled with zeroes. + */ + Report (Type type, + DeviceIndex device_index, + uint8_t sub_id, + uint8_t address); + /** + * HID++ 1.0 constructor from parameters. + * + * The type of the report is guessed from \p params size. + * + * \throws InvalidReportLength + */ + Report (DeviceIndex device_index, + uint8_t sub_id, + uint8_t address, + const std::vector ¶ms); + + /** + * Access HID++ 1.0 report subID. + */ + uint8_t subID () const; + /** + * Set HID++ 1.0 report subID. + */ + void setSubID (uint8_t sub_id); + + /** + * Access HID++ 1.0 report address. + */ + uint8_t address () const; + /** + * Set HID++ 1.0 report address. + */ + void setAddress (uint8_t address); + + /** + * Check if the report is a HID++ 1.0 error report. + * + * Each pointer can be null and is then ignored. + * + * \param sub_id The subID of the report responsible for the error. + * \param address The address of the report responsible for the error. + * \param error_code The error code of the error. + * + * \return \c true if the report is a HID++ 1.0 error, \c false otherwise. + * + * \sa HIDPP10::Error + */ + bool checkErrorMessage10 (uint8_t *sub_id, uint8_t *address, uint8_t *error_code) const; + + /**@}*/ + + /** + * \name HID++ 2.0 + * + * @{ + */ + + /** + * HID++ 2.0 constructor from type. + * + * Parameters size is set from \p type and is filled with zeroes. + */ + Report (Type type, + DeviceIndex device_index, + uint8_t feature_index, + unsigned int function, + unsigned int sw_id); + /** + * HID++ 2.0 constructor from parameters. + * + * The type of the report is guessed from \p params size. + * + * \throws InvalidReportLength + */ + Report (DeviceIndex device_index, + uint8_t feature_index, + unsigned int function, + unsigned int sw_id, + const std::vector ¶ms); + + /** + * Access HID++ 2.0 report feature index. + */ + uint8_t featureIndex () const; + /** + * Set HID++ 2.0 report feature index. + */ + void setfeatureIndex (uint8_t feature_index); + + /** + * Access HID++ 2.0 report function. + */ + unsigned int function () const; + /** + * Set HID++ 2.0 report function. + */ + void setFunction (unsigned int function); + + /** + * Access HID++ 2.0 software ID. + */ + unsigned int softwareID () const; + /** + * Set HID++ 2.0 software ID. + */ + void setSoftwareID (unsigned int sw_id); + + /** + * Check if the report is a HID++ 2.0 error report. + * + * Each pointer can be null and is then ignored. + * + * \param feature_index The feature index of the report responsible for the error. + * \param function The function of the report responsible for the error. + * \param sw_id The software ID of the report responsible for the error. + * \param error_code The error code of the error. + * + * \return \c true if the report is a HID++ 2.0 error, \c false otherwise. + * + * \sa HIDPP20::Error + */ + bool checkErrorMessage20 (uint8_t *feature_index, unsigned int *function, unsigned int *sw_id, uint8_t *error_code) const; + + /**@}*/ + + /** + * Get the parameter length of the report. + */ + std::size_t paramLength () const; + /** + * Get the parameter length for \p type. + */ + static std::size_t paramLength (Type type); + + /** + * Access the report parameters. + */ + std::vector ¶ms (); + /** + * Read-only access to the report parameters. + */ + const std::vector ¶ms () const; + + /** + * Get the raw HID report (without the ID). + */ + const std::vector rawReport () const; + +private: + static constexpr std::size_t HeaderLength = 4; + std::array _header; + std::vector _params; +}; + +} + +#endif + diff --git a/hidpp/hidpp/defs.h b/hidpp/hidpp/defs.h new file mode 100755 index 0000000..122eeda --- /dev/null +++ b/hidpp/hidpp/defs.h @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP_DEFS_H +#define HIDPP_DEFS_H + +#include +#include + +namespace HIDPP +{ + /** + * \defgroup hidpp HID++ + * @{ + */ + + /** + * Short HID++ report parameter length. + */ + constexpr std::size_t ShortParamLength = 3; + /** + * Long HID++ report parameter length. + */ + constexpr std::size_t LongParamLength = 16; + + /** + * HID++ device index. + * + * Receiver and wireless devices share the same + * hidraw device. The device index is used to direct + * the HID++ report to a particular device. + */ + enum DeviceIndex: uint8_t { + DefaultDevice = 0xff, // used by receiver or corded or bluetooth devices + CordedDevice = 0, // used by older corded devices + WirelessDevice1 = 1, + WirelessDevice2 = 2, + WirelessDevice3 = 3, + WirelessDevice4 = 4, + WirelessDevice5 = 5, + WirelessDevice6 = 6, + }; + + /**@}*/ +} + +#endif diff --git a/hidpp/hidpp/ids.h b/hidpp/hidpp/ids.h new file mode 100755 index 0000000..91141a0 --- /dev/null +++ b/hidpp/hidpp/ids.h @@ -0,0 +1,40 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP_IDS_H +#define HIDPP_IDS_H + +#include + +namespace HIDPP +{ + namespace ID { + constexpr uint16_t G5 = 0xc041; + constexpr uint16_t G5_2007 = 0xc049; + constexpr uint16_t G7 = 0xc51a; // or is it the receiver ID? + constexpr uint16_t G9 = 0xc048; + constexpr uint16_t G9x = 0xc066; + constexpr uint16_t G9x_MW3 = 0xc249; + constexpr uint16_t G500 = 0xc068; + constexpr uint16_t G700 = 0xc06b; + constexpr uint16_t G500s = 0xc24e; + constexpr uint16_t G700s = 0xc07c; + } +} + +#endif diff --git a/hidpp/hidpp10/Address.cpp b/hidpp/hidpp10/Address.cpp new file mode 100755 index 0000000..66655a9 --- /dev/null +++ b/hidpp/hidpp10/Address.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +using namespace HIDPP10; + +bool Address::operator< (const Address &other) const +{ + return page < other.page || + (page == other.page && offset < other.offset); +} + diff --git a/hidpp/hidpp10/Address.h b/hidpp/hidpp10/Address.h new file mode 100755 index 0000000..9e755ba --- /dev/null +++ b/hidpp/hidpp10/Address.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_ADDRESS_H +#define HIDPP10_ADDRESS_H + +#include + +namespace HIDPP10 +{ + +struct Address { + uint8_t page; + uint8_t offset; + + bool operator< (const Address &other) const; +}; + +} + +#endif + diff --git a/hidpp/hidpp10/Device.cpp b/hidpp/hidpp10/Device.cpp new file mode 100755 index 0000000..809789d --- /dev/null +++ b/hidpp/hidpp10/Device.cpp @@ -0,0 +1,195 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include +#include +#include +#include + +using namespace HIDPP10; + +Device::Device (const std::string &path, HIDPP::DeviceIndex device_index): + HIDPP::Device (path, device_index) +{ + // TODO: check version +} + +template +void Device::accessRegister (uint8_t address, + const std::vector *params, + std::vector *results) +{ + std::vector in; + if (params) { + in = *params; + in.resize (params_length, 0); + } + else { + in.resize (params_length, 0); + } + HIDPP::Report request (deviceIndex (), + sub_id, address, in); + sendReport (request); + + while (true) { + HIDPP::Report response = getReport (); + + uint8_t r_sub_id, r_address, error_code; + if (response.checkErrorMessage10 (&r_sub_id, &r_address, &error_code)) { + if (r_sub_id != sub_id || r_address != address) { + Log::debug () << __FUNCTION__ << ": " + << "Ignored error message with wrong subID or address" + << std::endl; + continue; + } + + Log::printf (Log::Debug, "Received error message with code 0x%02hhx\n", error_code); + throw Error (static_cast (error_code)); + } + + if (response.subID () != sub_id || + response.address () != address) { + Log::debug () << __FUNCTION__ << ": " + << "Ignored report with wrong subID or address" + << std::endl; + continue; + } + + if (response.paramLength () != results_length) + throw std::runtime_error ("Invalid result length"); + + if (results) + *results = response.params (); + return; + } +} + +void Device::setRegister (uint8_t address, + const std::vector ¶ms, + std::vector *results) +{ + if (params.size () <= HIDPP::ShortParamLength) { + Log::printf (Log::Debug, "Setting short register 0x%02hhx\n", address); + Log::printBytes (Log::Debug, "Parameters:", + params.begin (), params.end ()); + + accessRegister + (address, ¶ms, results); + + if (results) + Log::printBytes (Log::Debug, "Results:", + results->begin (), results->end ()); + } + else if (params.size () <= HIDPP::LongParamLength) { + Log::printf (Log::Debug, "Setting long register 0x%02hhx\n", address); + Log::printBytes (Log::Debug, "Parameters:", + params.begin (), params.end ()); + + accessRegister + (address, ¶ms, results); + + if (results) + Log::printBytes (Log::Debug, "Results:", + results->begin (), results->end ()); + } + else + throw std::logic_error ("Register too long"); + +} + +void Device::getRegister (uint8_t address, + const std::vector *params, + std::vector &results) +{ + if (results.size () <= HIDPP::ShortParamLength) { + Log::printf (Log::Debug, "Getting short register 0x%02hhx\n", address); + if (params) + Log::printBytes (Log::Debug, "Parameters:", + params->begin (), params->end ()); + + accessRegister + (address, params, &results); + + Log::printBytes (Log::Debug, "Results:", + results.begin (), results.end ()); + } + else if (results.size () <= HIDPP::LongParamLength) { + Log::printf (Log::Debug, "Getting long register 0x%02hhx\n", address); + if (params) + Log::printBytes (Log::Debug, "Parameters:", + params->begin (), params->end ()); + + accessRegister + (address, params, &results); + + Log::printBytes (Log::Debug, "Results:", + results.begin (), results.end ()); + } + else + throw std::logic_error ("Register too long"); +} + +void Device::sendDataPacket (uint8_t sub_id, uint8_t seq_num, + const std::vector ¶ms, + bool wait_for_ack) +{ + Log::printf (Log::Debug, "Sending data packet %hhu\n", seq_num); + Log::printBytes (Log::Debug, "Data packet", params.begin (), params.end ()); + + HIDPP::Report packet (deviceIndex (), + sub_id, + seq_num, + params); + sendReport (packet); + + if (!wait_for_ack) + return; + + while (true) { + HIDPP::Report response = getReport (); + + if (response.deviceIndex () != deviceIndex () || + response.subID () != SendDataAcknowledgement) { + Log::debug () << __FUNCTION__ << ": " + << "Ignored notification with wrong deviceIndex or subID" + << std::endl; + continue; + } + + if (response.address () == 1 && response.params ()[0] == seq_num) { + /* Expected notification */ + Log::printf (Log::Debug, "Data packet %hhu acknowledged\n", seq_num); + return; + } + else { + /* The notification is an error message */ + Log::printf (Log::Debug, "Data packet %hhu: error 0x%02hhx\n", + seq_num, response.address ()); + throw WriteError (response.address ()); + } + } +} + diff --git a/hidpp/hidpp10/Device.h b/hidpp/hidpp10/Device.h new file mode 100755 index 0000000..bf78a94 --- /dev/null +++ b/hidpp/hidpp10/Device.h @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_DEVICE_H +#define HIDPP10_DEVICE_H + +#include +#include + +namespace HIDPP10 +{ + +class Device: public HIDPP::Device +{ +public: + Device (const std::string &path, HIDPP::DeviceIndex device_index = HIDPP::DefaultDevice); + + void setRegister (uint8_t address, + const std::vector ¶ms, + std::vector *results); + void getRegister (uint8_t address, + const std::vector *params, + std::vector &results); + + void sendDataPacket (uint8_t sub_id, uint8_t seq_num, + const std::vector ¶ms, + bool wait_for_ack = false); +private: + template + void accessRegister (uint8_t address, + const std::vector *params, + std::vector *results); +}; + +} + +#endif diff --git a/hidpp/hidpp10/DeviceInfo.h b/hidpp/hidpp10/DeviceInfo.h new file mode 100755 index 0000000..ce3859f --- /dev/null +++ b/hidpp/hidpp10/DeviceInfo.h @@ -0,0 +1,53 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_DEVICE_INFO_H +#define HIDPP10_DEVICE_INFO_H + +#include + +#include +#include +#include + +namespace HIDPP10 +{ + struct MouseInfo: HIDPP::DeviceInfo + { + const Sensor *sensor; + IResolutionType iresolution_type; + ProfileType profile_type; + + MouseInfo (const Sensor *sensor, + IResolutionType iresolution_type, + ProfileType profile_type): + HIDPP::DeviceInfo (HIDPP::DeviceInfo::Device), + sensor (sensor), + iresolution_type (iresolution_type), + profile_type (profile_type) + { + } + }; + + inline const MouseInfo *getMouseInfo (uint16_t product_id) + { + return dynamic_cast (HIDPP::getDeviceInfo (product_id)); + } +} + +#endif diff --git a/hidpp/hidpp10/Error.cpp b/hidpp/hidpp10/Error.cpp new file mode 100755 index 0000000..2662243 --- /dev/null +++ b/hidpp/hidpp10/Error.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +using namespace HIDPP10; + +Error::Error (ErrorCode error_code): + _error_code (error_code) +{ +} + +const char *Error::what () const noexcept +{ + switch (_error_code) { + case Success: + return "Success"; + case InvalidSubID: + return "Invalid sub ID"; + case InvalidAddress: + return "Invalid address"; + case InvalidValue: + return "Invalid value"; + case ConnectFail: + return "Connect fail"; + case TooManyDevices: + return "Too many devices"; + case AlreadyExists: + return "Already exists"; + case Busy: + return "Busy"; + case UnknownDevice: + return "Unknown device"; + case ResourceError: + return "Resource error"; + case RequestUnavailable: + return "Request unavailable"; + case InvalidParamValue: + return "Invalid param value"; + case WrongPINCode: + return "Wrong PIN code"; + default: + return "Unknown error code"; + } +} + +Error::ErrorCode Error::errorCode () const +{ + return _error_code; +} + diff --git a/hidpp/hidpp10/Error.h b/hidpp/hidpp10/Error.h new file mode 100755 index 0000000..e7af32a --- /dev/null +++ b/hidpp/hidpp10/Error.h @@ -0,0 +1,59 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_ERROR_H +#define HIDPP10_ERROR_H + +#include + +#include + +namespace HIDPP10 +{ + +class Error: public std::exception +{ +public: + enum ErrorCode: uint8_t { + Success = 0x00, + InvalidSubID = 0x01, + InvalidAddress = 0x02, + InvalidValue = 0x03, + ConnectFail = 0x04, + TooManyDevices = 0x05, + AlreadyExists = 0x06, + Busy = 0x07, + UnknownDevice = 0x08, + ResourceError = 0x09, + RequestUnavailable = 0x0A, + InvalidParamValue = 0x0B, + WrongPINCode = 0x0C, + }; + + Error (ErrorCode error_code); + + virtual const char *what () const noexcept; + ErrorCode errorCode () const; + +private: + ErrorCode _error_code; +}; + +} + +#endif diff --git a/hidpp/hidpp10/IIndividualFeatures.cpp b/hidpp/hidpp10/IIndividualFeatures.cpp new file mode 100755 index 0000000..2ff583e --- /dev/null +++ b/hidpp/hidpp10/IIndividualFeatures.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include + +using namespace HIDPP10; + +IIndividualFeatures::IIndividualFeatures (Device *dev): + _dev (dev) +{ +} + +unsigned int IIndividualFeatures::flags () +{ + std::vector results (HIDPP::ShortParamLength); + _dev->getRegister (EnableIndividualFeatures, nullptr, results); + unsigned int flags = 0; + for (unsigned int i = 0; i < 3; ++i) + flags |= results[i] << (i*8); + return flags; +} + +void IIndividualFeatures::setFlags (unsigned int f) +{ + std::vector params (HIDPP::ShortParamLength); + for (unsigned int i = 0; i < 3; ++i) + params[i] = (f >> (i*8)) & 0xFF; + _dev->setRegister (EnableIndividualFeatures, params, nullptr); +} + +bool IIndividualFeatures::hasFlag (IndividualFeature feature) +{ + return flags () & feature; +} + +void IIndividualFeatures::setFlag (IndividualFeature feature) +{ + unsigned int f = flags (); + f |= feature; + setFlags (f); +} + +void IIndividualFeatures::unsetFlag (IndividualFeature feature) +{ + unsigned int f = flags (); + f &= ~feature; + setFlags (f); +} + diff --git a/hidpp/hidpp10/IIndividualFeatures.h b/hidpp/hidpp10/IIndividualFeatures.h new file mode 100755 index 0000000..147f1af --- /dev/null +++ b/hidpp/hidpp10/IIndividualFeatures.h @@ -0,0 +1,58 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_IINDIVIDUALFEATURES_H +#define HIDPP10_IINDIVIDUALFEATURES_H + +namespace HIDPP10 +{ + +class Device; + +class IIndividualFeatures +{ +public: + enum IndividualFeature: unsigned int { + SpecialButtonFunction = 1<<1, + EnhancedKeyUsage = 1<<2, + FastForwardRewind = 1<<3, + ScrollingAcceleration = 1<<6, + ButtonsControlResolution = 1<<7, + InhibitLockKeySound = 1<<16, + MXAir3DEngine = 1<<18, + LEDControl = 1<<19, + }; + + IIndividualFeatures (Device *dev); + + unsigned int flags (); + void setFlags (unsigned int flags); + + bool hasFlag (IndividualFeature feature); + void setFlag (IndividualFeature feature); + void unsetFlag (IndividualFeature feature); + +private: + Device *_dev; +}; + +} + +#endif + + diff --git a/hidpp/hidpp10/IMemory.cpp b/hidpp/hidpp10/IMemory.cpp new file mode 100755 index 0000000..a8f4f14 --- /dev/null +++ b/hidpp/hidpp10/IMemory.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include + +#include + +using namespace HIDPP10; + +IMemory::IMemory (Device *dev): + _dev (dev) +{ +} + +int IMemory::readSome (Address address, uint8_t *buffer, std::size_t maxlen) +{ + std::vector params (HIDPP::ShortParamLength); + params[0] = address.page; + params[1] = address.offset; + std::vector results (HIDPP::LongParamLength); + _dev->getRegister (MemoryRead, ¶ms, results); + std::size_t len = std::min (HIDPP::LongParamLength, maxlen); + std::copy (results.begin (), results.begin () + len, buffer); + return len; +} + +void IMemory::readMem (Address address, std::vector &data) +{ + std::size_t read = 0; + while (read < data.size ()) { + std::size_t len = readSome (address, &data[read], data.size () - read); + address.offset += len/2; + read += len; + } +} + +void IMemory::writeMem (Address address, const std::vector &data) +{ + static constexpr std::size_t HeaderLength = 9; + static constexpr std::size_t FirstPacketDataLength = + HIDPP::LongParamLength - HeaderLength; + + /* + * Init sequence number + */ + resetSequenceNumber (); + + /* + * Start sending packets + */ + bool first = true; + std::size_t sent = 0; + uint8_t seq_num = 0; + while (sent < data.size ()) { + uint8_t sub_id; + std::vector params (HIDPP::LongParamLength); + if (first) { + sub_id = SendDataBeginAck; + /* First packet header */ + params[0] = 0x01; // Unknown meaning + params[1] = address.page; + params[2] = address.offset; + writeBE (params, 5, data.size ()); + /* Start of data */ + if (data.size () < FirstPacketDataLength) { + std::copy (data.begin (), data.end (), + params.begin () + HeaderLength); + sent = data.size (); + } + else { + std::copy (data.begin (), + data.begin () + FirstPacketDataLength, + params.begin () + HeaderLength); + sent += FirstPacketDataLength; + } + first = false; + } + else { + sub_id = SendDataContinueAck; + if (data.size () < HIDPP::LongParamLength) { + std::copy (data.begin () + sent, + data.end (), + params.begin ()); + sent = data.size (); + } + else { + std::copy (data.begin () + sent, + data.begin () + sent + HIDPP::LongParamLength, + params.begin ()); + sent += HIDPP::LongParamLength; + } + } + _dev->sendDataPacket (sub_id, seq_num, params, true); + seq_num++; + } +} + +void IMemory::writePage (uint8_t page, const std::vector &data) +{ + if (data.size () > 512) + throw std::logic_error ("page too big"); + + fillPage (page); + writeMem ({page, 0}, data); +} + +void IMemory::resetSequenceNumber () +{ + std::vector params (HIDPP::ShortParamLength); + params[0] = 1; + _dev->setRegister (ResetSeqNum, params, nullptr); +} + +void IMemory::fillPage (uint8_t page) +{ + std::vector params (HIDPP::LongParamLength); + params[0] = Fill; + params[6] = page; + _dev->setRegister (MemoryOperation, params, nullptr); +} + diff --git a/hidpp/hidpp10/IMemory.h b/hidpp/hidpp10/IMemory.h new file mode 100755 index 0000000..0566bbc --- /dev/null +++ b/hidpp/hidpp10/IMemory.h @@ -0,0 +1,58 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_IMEMORY_H +#define HIDPP10_IMEMORY_H + +#include +#include + +#include + +namespace HIDPP10 +{ + +class Device; + +class IMemory +{ +public: + enum MemoryOp: uint8_t { + Fill = 2, + Copy = 3, + }; + + IMemory (Device *dev); + + int readSome (Address address, uint8_t *buffer, std::size_t maxlen); + void readMem (Address address, std::vector &data); + + + void writeMem (Address address, const std::vector &data); + void writePage (uint8_t page, const std::vector &data); + + void resetSequenceNumber (); + void fillPage (uint8_t page); + +private: + Device *_dev; +}; + +} + +#endif diff --git a/hidpp/hidpp10/IProfile.cpp b/hidpp/hidpp10/IProfile.cpp new file mode 100755 index 0000000..122c7ac --- /dev/null +++ b/hidpp/hidpp10/IProfile.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include + +using namespace HIDPP10; + +IProfile::IProfile (Device *dev): + _dev (dev) +{ +} + +int IProfile::activeProfile () +{ + std::vector results (HIDPP::ShortParamLength); + _dev->getRegister (CurrentProfile, nullptr, results); + if (results[0] == FactoryDefault) + return -1; + if (results[0] == ProfileIndex) + return results[1]; + throw std::runtime_error ("Invalid active profile type."); +} + +void IProfile::loadFactoryDefault () +{ + std::vector params (HIDPP::ShortParamLength); + params[0] = FactoryDefault; + _dev->setRegister (CurrentProfile, params, nullptr); +} + +void IProfile::loadProfileFromIndex (unsigned int index) +{ + std::vector params (HIDPP::ShortParamLength); + params[0] = ProfileIndex; + params[1] = index; + _dev->setRegister (CurrentProfile, params, nullptr); +} + +void IProfile::loadProfileFromAddress (Address address) +{ + std::vector params (HIDPP::ShortParamLength); + params[0] = ProfileAddress; + params[1] = address.page; + params[2] = address.offset; + _dev->setRegister (CurrentProfile, params, nullptr); +} + +void IProfile::reloadActiveProfile () +{ + std::vector current_value (HIDPP::ShortParamLength); + _dev->getRegister (CurrentProfile, nullptr, current_value); + _dev->setRegister (CurrentProfile, current_value, nullptr); +} + diff --git a/hidpp/hidpp10/IProfile.h b/hidpp/hidpp10/IProfile.h new file mode 100755 index 0000000..04847b8 --- /dev/null +++ b/hidpp/hidpp10/IProfile.h @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_IPROFILE_H +#define HIDPP10_IPROFILE_H + +#include + +namespace HIDPP10 +{ + +class Device; + +class IProfile +{ +public: + IProfile (Device *dev); + + enum ProfileType: uint8_t { + ProfileIndex = 0x00, + ProfileAddress = 0x01, + FactoryDefault = 0xFF, + }; + + /** + * Get active profile. + * + * Returns -1 for factory default, or a positive + * integer for current profile index. + */ + int activeProfile (); + + void loadFactoryDefault (); + void loadProfileFromIndex (unsigned int index); + void loadProfileFromAddress (Address address); + + void reloadActiveProfile (); + +private: + Device *_dev; +}; + +} + +#endif + + diff --git a/hidpp/hidpp10/IReceiver.cpp b/hidpp/hidpp10/IReceiver.cpp new file mode 100755 index 0000000..be8869c --- /dev/null +++ b/hidpp/hidpp10/IReceiver.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include + +#include +#include + +using namespace HIDPP10; + +IReceiver::IReceiver (Device *dev): + _dev (dev) +{ +} + +void IReceiver::getDeviceInformation (unsigned int device, + uint8_t *destination_id, + uint8_t *report_interval, + uint16_t *wpid, + DeviceType *type) +{ + if (device >= 16) + throw std::out_of_range ("Device index too big"); + + std::vector params (HIDPP::ShortParamLength), + results (HIDPP::LongParamLength); + + params[0] = DeviceInformation | (device & 0x0F); + + _dev->getRegister (DevicePairingInfo, ¶ms, results); + + if (params[0] != results[0]) + throw std::runtime_error ("Invalid DevicePairingInfo type"); + + if (destination_id) + *destination_id = results[1]; + if (report_interval) + *report_interval = results[2]; + if (wpid) + *wpid = readBE (results, 3); + if (type) + *type = static_cast (results[7]); +} + +void IReceiver::getDeviceExtendedInformation (unsigned int device, + uint32_t *serial, + uint32_t *report_types, + PowerSwitchLocation *ps_loc) +{ + if (device >= 16) + throw std::out_of_range ("Device index too big"); + + std::vector params (HIDPP::ShortParamLength), + results (HIDPP::LongParamLength); + + params[0] = ExtendedDeviceInformation | (device & 0x0F); + + _dev->getRegister (DevicePairingInfo, ¶ms, results); + + if (params[0] != results[0]) + throw std::runtime_error ("Invalid DevicePairingInfo type"); + + if (serial) + *serial = readBE (results, 1); + if (report_types) + *report_types = readBE (results, 5); + if (ps_loc) + *ps_loc = static_cast (results[9] & 0x0F); +} + +std::string IReceiver::getDeviceName (unsigned int device) +{ + if (device >= 16) + throw std::out_of_range ("Device index too big"); + + std::vector params (HIDPP::ShortParamLength), + results (HIDPP::LongParamLength); + + params[0] = DeviceName | (device & 0x0F); + + _dev->getRegister (DevicePairingInfo, ¶ms, results); + + if (params[0] != results[0]) + throw std::runtime_error ("Invalid DevicePairingInfo type"); + + std::size_t length = results[1]; + return std::string (reinterpret_cast (&results[2]), std::min (length, 14ul)); +} diff --git a/hidpp/hidpp10/IReceiver.h b/hidpp/hidpp10/IReceiver.h new file mode 100755 index 0000000..e8ce18e --- /dev/null +++ b/hidpp/hidpp10/IReceiver.h @@ -0,0 +1,84 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_IRECEIVER_H +#define HIDPP10_IRECEIVER_H + +#include +#include + +namespace HIDPP10 +{ + +class Device; + +class IReceiver +{ +public: + enum InformationType: uint8_t { + DeviceInformation = 0x20, + ExtendedDeviceInformation = 0x30, + DeviceName = 0x40, + }; + + enum DeviceType: uint8_t { + Unknown = 0x00, + Keyboard = 0x01, + Mouse = 0x02, + Numpad = 0x03, + Presenter = 0x04, + Trackball = 0x08, + Touchpad = 0x09, + }; + + enum PowerSwitchLocation { + Base = 0x1, + TopCase = 0x2, + TopRightCornerEdge = 0x3, + Other = 0x4, + TopLeftCorner = 0x5, + BottomLeftCorner = 0x6, + TopRightCorner = 0x7, + BottomRightCorner = 0x8, + TopEdge = 0x9, + RightEdge = 0xA, + LeftEdge = 0xB, + BottomEdge = 0xC, + }; + + IReceiver (Device *dev); + + void getDeviceInformation (unsigned int device, + uint8_t *destination_id, + uint8_t *report_interval, + uint16_t *wpid, + DeviceType *type); + void getDeviceExtendedInformation (unsigned int device, + uint32_t *serial, + uint32_t *report_types, + PowerSwitchLocation *ps_loc); + std::string getDeviceName (unsigned int device); + +private: + Device *_dev; +}; + +} + +#endif + diff --git a/hidpp/hidpp10/IResolution.cpp b/hidpp/hidpp10/IResolution.cpp new file mode 100755 index 0000000..0c4411b --- /dev/null +++ b/hidpp/hidpp10/IResolution.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include +#include + +#include + +using namespace HIDPP10; + +IResolution0::IResolution0 (Device *dev, const Sensor *sensor): + _dev (dev), _sensor (sensor) +{ +} + +unsigned int IResolution0::getCurrentResolution () +{ + std::vector results (HIDPP::ShortParamLength); + _dev->getRegister (SensorResolution, nullptr, results); + return _sensor->toDPI (results[0]); +} + +void IResolution0::setCurrentResolution (unsigned int dpi) +{ + std::vector params (HIDPP::ShortParamLength); + params[0] = _sensor->fromDPI (dpi); + _dev->setRegister (SensorResolution, params, nullptr); +} + +IResolution3::IResolution3 (Device *dev, const Sensor *sensor): + _dev (dev), _sensor (sensor) +{ +} + +void IResolution3::getCurrentResolution (unsigned int &x_dpi, unsigned int &y_dpi) +{ + std::vector results (HIDPP::LongParamLength); + _dev->getRegister (SensorResolution, nullptr, results); + x_dpi = _sensor->toDPI (readLE (results, 0)); + y_dpi = _sensor->toDPI (readLE (results, 2)); +} + +void IResolution3::setCurrentResolution (unsigned int x_dpi, unsigned int y_dpi) +{ + std::vector params (HIDPP::LongParamLength); + writeLE (params, 0, _sensor->fromDPI (x_dpi)); + writeLE (params, 2, _sensor->fromDPI (y_dpi)); + _dev->setRegister (SensorResolution, params, nullptr); +} + +bool IResolution3::getAngleSnap () +{ + std::vector results (HIDPP::LongParamLength); + _dev->getRegister (SensorResolution, nullptr, results); + switch (results[5]) { + case 0x01: + return false; + case 0x02: + return true; + default: + throw std::runtime_error ("Invalid angle snap value"); + } +} + +void IResolution3::setAngleSnap (bool angle_snap) +{ + std::vector params (HIDPP::LongParamLength); + params[5] = angle_snap ? 0x02 : 0x01; + _dev->setRegister (SensorResolution, params, nullptr); +} + diff --git a/hidpp/hidpp10/IResolution.h b/hidpp/hidpp10/IResolution.h new file mode 100755 index 0000000..e845e93 --- /dev/null +++ b/hidpp/hidpp10/IResolution.h @@ -0,0 +1,64 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_IRESOLUTION_H +#define HIDPP10_IRESOLUTION_H + +namespace HIDPP10 +{ + +enum IResolutionType { + IResolutionType0, + IResolutionType3, +}; + +class Device; +class Sensor; + +class IResolution0 +{ +public: + IResolution0 (Device *dev, const Sensor *sensor); + + unsigned int getCurrentResolution (); + void setCurrentResolution (unsigned int dpi); + +private: + Device *_dev; + const Sensor *_sensor; +}; + +class IResolution3 +{ +public: + IResolution3 (Device *dev, const Sensor *sensor); + + void getCurrentResolution (unsigned int &x_dpi, unsigned int &y_dpi); + void setCurrentResolution (unsigned int x_dpi, unsigned int y_dpi); + + bool getAngleSnap (); + void setAngleSnap (bool angle_snap); + +private: + Device *_dev; + const Sensor *_sensor; +}; + +} + +#endif diff --git a/hidpp/hidpp10/Macro.cpp b/hidpp/hidpp10/Macro.cpp new file mode 100755 index 0000000..6b4450d --- /dev/null +++ b/hidpp/hidpp10/Macro.cpp @@ -0,0 +1,969 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace HIDPP10; + +Macro::Item::Item (uint8_t op_code): + _op_code (op_code) +{ +} + +std::size_t Macro::Item::getOpLength (uint8_t op_code) +{ + switch (op_code & 0xE0) { + case 0x00: + return 1; + case 0x20: + return 2; + case 0x40: + return 3; + case 0x60: + return 5; + default: + return 1; + } +} + +uint8_t Macro::Item::getShortDelayCode (unsigned int delay) +{ + if (delay < 8) + return 0x80; // Minimum short delay of 8ms + else if (delay < 132) + return 0x80 + (delay - 8 + 2) / 4; + else if (delay < 388) + return 0x9F + (delay - 132 + 4) / 8; + else if (delay < 900) + return 0xBF + (delay - 388 + 8) / 16; + else if (delay < 1892) + return 0xDF + (delay - 900 + 16) / 32; + else + return 0xFE; // Maximum short delay of 1.892s +} + +unsigned int Macro::Item::getShortDelayDuration (uint8_t op_code) +{ + if (op_code < 0x80) + return 0; // Not a short delay op code + else if (op_code <= 0x9F) + return 8 + (op_code - 0x80) * 4; + else if (op_code <= 0xBF) + return 132 + (op_code - 0x9F) * 8; + else if (op_code <= 0xDF) + return 388 + (op_code - 0xBF) * 16; + else if (op_code <= 0xFE) + return 900 + (op_code - 0xDF) * 32; + else + return 0; // Not a short delay op code +} + +uint8_t Macro::Item::opCode () const +{ + return _op_code; +} + +bool Macro::Item::isShortDelay () const +{ + return _op_code >= 0x80 && _op_code <= 0xFE; +} + +uint8_t Macro::Item::keyCode () const +{ + return _params.key; +} + +void Macro::Item::setKeyCode (uint8_t key) +{ + if (_op_code == KeyPress || _op_code == KeyRelease) + _params.key = key; +} + +uint8_t Macro::Item::modifiers () const +{ + return _params.modifiers; +} + +void Macro::Item::setModifiers (uint8_t modifiers) +{ + if (_op_code == ModifierPress || _op_code == ModifierRelease) + _params.modifiers = modifiers; +} + +int8_t Macro::Item::wheel () const +{ + return _params.wheel; +} + +void Macro::Item::setWheel (int8_t wheel) +{ + if (_op_code == MouseWheel) + _params.wheel = wheel; +} + +uint16_t Macro::Item::buttons () const +{ + return _params.buttons; +} + +void Macro::Item::setButtons (uint16_t buttons) +{ + if (_op_code == MouseButtonPress || _op_code == MouseButtonRelease) + _params.buttons = buttons; +} + +uint16_t Macro::Item::consumerControl () const +{ + return _params.cc; +} + +void Macro::Item::setConsumerControl (uint16_t cc) +{ + if (_op_code == ConsumerControl) + _params.cc = cc; +} + +unsigned int Macro::Item::delay () const +{ + if (_op_code >= 0x80 && _op_code <= 0xFE) // Short delay + return getShortDelayDuration (_op_code); + return _params.delay; +} + +void Macro::Item::setDelay (unsigned int delay) +{ + if (_op_code == Delay || _op_code == JumpIfReleased) + _params.delay = delay; + else if (_op_code >= 0x80 && _op_code <= 0xFE) // Short delay + _op_code = getShortDelayCode (delay); +} + +bool Macro::Item::isJump () const +{ + return _op_code == Jump || + _op_code == JumpIfPressed || + _op_code == JumpIfReleased; +} + +std::list::iterator Macro::Item::jumpDestination () +{ + return _dest; +} + +std::list::const_iterator Macro::Item::jumpDestination () const +{ + return _dest; +} + +void Macro::Item::setJumpDestination (std::list::iterator dest) +{ + if (_op_code == Jump || + _op_code == JumpIfPressed || + _op_code == JumpIfReleased) + _dest = dest; +} + +int Macro::Item::mouseX () const +{ + return _params.mouse.x; +} + +int Macro::Item::mouseY () const +{ + return _params.mouse.y; +} + +void Macro::Item::setMouseX (int delta) +{ + if (_op_code == MousePointer) + _params.mouse.x = delta; +} + +void Macro::Item::setMouseY (int delta) +{ + if (_op_code == MousePointer) + _params.mouse.y = delta; +} + +bool Macro::Item::isSimple () const +{ + if (_op_code >= 0x80 && _op_code <= 0xfe) // Short delay + return true; + switch (_op_code) { + case NoOp: + case KeyPress: + case KeyRelease: + case ModifierPress: + case ModifierRelease: + case MouseWheel: + case MouseButtonPress: + case MouseButtonRelease: + case ConsumerControl: + case Delay: + case MousePointer: + return true; + default: + return false; + } +} + +Macro::Macro () +{ +} + +static Macro::Item parseItem (std::vector::const_iterator begin, + Address &jump_dest) +{ + Macro::Item item (*begin); + + switch (*begin) { + case Macro::Item::KeyPress: + case Macro::Item::KeyRelease: + item.setKeyCode (*(begin+1)); + return item; + + case Macro::Item::ModifierPress: + case Macro::Item::ModifierRelease: + item.setModifiers (*(begin+1)); + return item; + + case Macro::Item::MouseWheel: + item.setWheel (*reinterpret_cast (&*(begin+1))); + return item; + + case Macro::Item::MouseButtonPress: + case Macro::Item::MouseButtonRelease: + item.setButtons (readLE (begin+1)); + return item; + + case Macro::Item::ConsumerControl: + item.setConsumerControl (readBE (begin+1)); + return item; + + case Macro::Item::Delay: + item.setDelay (readBE (begin+1)); + return item; + + case Macro::Item::Jump: + case Macro::Item::JumpIfPressed: + jump_dest.page = *(begin+1); + jump_dest.offset = *(begin+2); + return item; + + case Macro::Item::MousePointer: + item.setMouseX (readBE (begin+1)); + item.setMouseY (readBE (begin+3)); + return item; + + case Macro::Item::JumpIfReleased: + item.setDelay (readBE (begin+1)); + jump_dest.page = *(begin+3); + jump_dest.offset = *(begin+4); + return item; + + default: + return item; + } +} + +Macro::Macro (MemoryMapping &mem, Address address) +{ + Log::printf (Log::Debug, + "Reading macro (%p) in persistent memory at address %02hhx:%02hhx\n", + this, + address.page, address.offset); + std::map parsed_items; + std::vector> incomplete_ref; + + unsigned int current_page = address.page; + unsigned int current_index = 2*address.offset; + + std::stack
jump_dests; + while (true) { + const std::vector &data = mem.getReadOnlyPage (current_page); + Address dest; + _items.push_back (parseItem (data.begin () + current_index, dest)); + Item *item = &_items.back (); + Log::printf (Log::Debug, + "Parsed macro item %p at page %02hhx, index %03x, op_code is %02hhx\n", + item, + current_page, current_index, + item->opCode ()); + + // Memorize aligned items by address + if (current_index % 2 == 0) { + Address addr = { + static_cast (current_page), + static_cast (current_index/2) + }; + Log::printf (Log::Debug, + "Macro item %p is aligned at %02hhx:%02hhx\n", + item, + addr.page, addr.offset); + parsed_items.insert ({ + addr, + std::prev(_items.end ()) + }); + } + + if (item->isJump ()) { + jump_dests.push (dest); // Keep destination for later parsing + incomplete_ref.emplace_back (item, dest); + } + + if (item->opCode () == Item::End || item->opCode () == Item::Jump) { + // Current item has no successor + + // Find the first non parsed address + do { + if (jump_dests.empty ()) + goto parse_end; + dest = jump_dests.top (); + jump_dests.pop (); + } while (parsed_items.find (dest) != parsed_items.end ()); + + current_page = dest.page; + current_index = 2*dest.offset; + } + else { + current_index += Item::getOpLength (item->opCode ()); + } + } +parse_end: + + for (auto pair: incomplete_ref) { + // Find item iterator at referenced address + Item *item = pair.first; + Address &address = pair.second; + auto it = parsed_items[address]; + Log::printf (Log::Debug, + "Macro item %p references %02hhx:%02hhx: %p\n", + item, + address.page, address.offset, + &(*it)); + item->setJumpDestination (it); + } +} + +Macro::Macro (std::vector::const_iterator begin, Address start_address) +{ + std::map parsed_items; + std::vector> incomplete_ref; + + unsigned int current_page = start_address.page; + std::vector::const_iterator current = begin; + + std::stack
jump_dests; + while (true) { + Address dest; + _items.push_back (parseItem (current, dest)); + Item *item = &_items.back (); + + // Memorize aligned items by address + if ((current-begin) % 2 == 0) + parsed_items.insert ({ + Address ({ + static_cast (current_page), + static_cast (start_address.offset + + (current-begin)/2) + }), + std::prev (_items.end ()) + }); + + if (item->isJump ()) { + if (dest.page != start_address.page) + throw std::runtime_error ("Cannot parse macro referencing other pages"); + jump_dests.push (dest); // Keep destination for later parsing + incomplete_ref.emplace_back (item, dest); + } + + if (item->opCode () == Item::End || item->opCode () == Item::Jump) { + // Current item has no successor + + // Find the first non parsed address + do { + if (jump_dests.empty ()) + goto parse_end; + dest = jump_dests.top (); + jump_dests.pop (); + } while (parsed_items.find (dest) != parsed_items.end ()); + + current = begin + 2*(dest.offset - start_address.offset); + } + else { + current += Item::getOpLength (item->opCode ()); + } + } +parse_end: + + for (auto pair: incomplete_ref) { + // Find item iterator at referenced address + Item *item = pair.first; + Address &address = pair.second; + auto it = parsed_items[address]; + Log::printf (Log::Debug, + "Macro item %p references %02hhx:%02hhx: %p\n", + item, + address.page, address.offset, + &(*it)); + item->setJumpDestination (it); + } +} + +Macro::Macro (const Macro &other): + _items (other._items) +{ + Log::debug () << "Copying macro" << std::endl; + // The destination of every jump needs to be corrected in the copied list + std::map translation; + + const_iterator other_it = other._items.begin (); + for (iterator it = _items.begin (); + it != _items.end (); + ++it, ++other_it) { + Log::printf (Log::Debug, + "Add translation %p to %p\n", + &(*other_it), &(*it)); + + translation.insert ({ &(*other_it), it }); + } + + for (Item &item: _items) { + if (item.isJump ()) { + Item *old_item = &(*item.jumpDestination ()); + iterator new_item = translation[old_item]; + Log::printf (Log::Debug, + "Replace %p with %p\n", + old_item, &(*new_item)); + + item.setJumpDestination (new_item); + } + } +} + +static void writeItem (const Macro::Item &item, + std::vector::iterator begin, + std::vector::iterator *jump_addr = nullptr) +{ + uint8_t op_code = item.opCode (); + + *begin = op_code; + + switch (op_code) { + case Macro::Item::KeyPress: + case Macro::Item::KeyRelease: + *(begin+1) = item.keyCode (); + return; + + case Macro::Item::ModifierPress: + case Macro::Item::ModifierRelease: + *(begin+1) = item.modifiers (); + return; + + case Macro::Item::MouseWheel: + *reinterpret_cast (&*(begin+1)) = item.wheel (); + return; + + case Macro::Item::MouseButtonPress: + case Macro::Item::MouseButtonRelease: + writeLE (begin+1, item.buttons ()); + return; + + case Macro::Item::ConsumerControl: + writeBE (begin+1, item.consumerControl ()); + return; + + case Macro::Item::Delay: + writeBE (begin+1, item.delay ()); + return; + + case Macro::Item::Jump: + case Macro::Item::JumpIfPressed: + *jump_addr = begin+1; + return; + + case Macro::Item::MousePointer: + writeBE (begin+1, item.mouseX ()); + writeBE (begin+3, item.mouseY ()); + return; + + case Macro::Item::JumpIfReleased: + writeBE (begin+1, item.delay ()); + *jump_addr = begin+3; + return; + + default: + return; + } +} + +std::vector::iterator +Macro::write (std::vector::iterator begin, Address start_address) const +{ + typedef std::vector::iterator iterator; + std::map jump_dests; // Associate address with jump destination items + std::vector> jump_addrs; // Jumps and their address positions + + for (auto item: _items) { + if (item.isJump ()) { + jump_dests.emplace(&*item.jumpDestination (), Address ()); + } + } + + iterator current = begin; + + for (auto it = _items.begin (); it != _items.end (); ++it) { + const Item &item = *it; + + auto jump_dest = jump_dests.find (&item); + bool is_jump_dest = jump_dest != jump_dests.end (); + + // Add padding if required + if (is_jump_dest && (current-begin)%2 == 1) { + Log::printf (Log::Debug, + "Write padding at index %03x\n", + static_cast (current-begin)); + writeItem (Item (Item::NoOp), current); + ++current; + } + if (is_jump_dest) + Log::printf (Log::Debug, + "Macro item %p is aligned at %02hhx:%02hhx\n", + &item, + start_address.page, start_address.offset + static_cast ((current-begin)/2)); + + // Write the item itself + Log::printf (Log::Debug, + "Write macro item %p index %03x, op_code is %02hhx\n", + &item, + static_cast (current-begin), + item.opCode ()); + iterator addr; + writeItem (item, current, &addr); + + // Remember jump address position for later resolution + if (item.isJump ()) { + jump_addrs.emplace_back (&item, addr); + } + + // Remember item address for later jump resolution + if (is_jump_dest) { + jump_dest->second = Address { + static_cast (start_address.page), + static_cast (start_address.offset + (current-begin)/2) + }; + } + + current += Item::getOpLength (item.opCode ()); + } + + // Write jump addresses + for (auto jump_addr: jump_addrs) { + const Item *dest = &*jump_addr.first->jumpDestination (); + Address addr = jump_dests[dest]; + iterator addr_pos = jump_addr.second; + Log::printf (Log::Debug, + "Macro item %p jump to %02hhx:%02hhx\n", + jump_addr.first, + addr.page, addr.offset); + *addr_pos = addr.page; + *(addr_pos+1) = addr.offset; + } + + return current + (current-begin)%2; +} + +Address Macro::write (MemoryMapping &mem, Address start) const +{ + typedef std::vector::iterator iterator; + std::vector *page_data; + std::map jump_dests; // Associate address with jump destination items + std::vector> jump_addrs; // Jumps and their address positions + + for (auto item: _items) { + if (item.isJump ()) { + jump_dests.emplace(&*item.jumpDestination (), Address ()); + } + } + + unsigned int current_page = start.page; + unsigned int current_index = start.offset*2; + + constexpr std::size_t CRCSize = 2; + static const std::size_t JumpSize = Item::getOpLength (Item::Jump); + bool check_end_of_page_jump = true; + + for (auto it = _items.begin (); it != _items.end (); ++it) { + const Item &item = *it; + + auto jump_dest = jump_dests.find (&item); + bool is_jump_dest = jump_dest != jump_dests.end (); + + unsigned int item_size = Item::getOpLength (item.opCode ()); + + if (check_end_of_page_jump) { + if (is_jump_dest && current_index%2 == 1) { + // The current index is not aligned and the item is + // the destination of a jump. We need one byte of padding. + item_size++; + } + if (current_index + item_size > PageSize-CRCSize-JumpSize) { + // If we write this item now, there will not be enough + // room to write a jump. We check if the whole macro can + // fit before the end of page. + bool need_jump = false; + unsigned int index = current_index + item_size; + for (auto it2 = std::next(it); it2 != _items.end (); ++it2) { + if (index%2 == 1 && jump_dests.find (&*it2) != jump_dests.end ()) { + // Padding will be needed + ++index; + } + index += Item::getOpLength (it2->opCode ()); + if (index >= PageSize - CRCSize) { + // index reached end of page + need_jump = true; + break; + } + } + if (need_jump) { + // Jump to the beginning of the next page + Log::printf (Log::Debug, + "Write jump over end of page %02x at index %03x\n", + current_page, current_index); + page_data = &mem.getWritablePage (current_page); + iterator addr; + writeItem (Item (Item::Jump), page_data->begin () + current_index, &addr); + *addr = ++current_page; + *(addr+1) = current_index = 0; + } + else { + // The macro will fit in the current page, no need + // to check again. + check_end_of_page_jump = false; + } + } + } + + page_data = &mem.getWritablePage (current_page); + + // Add padding if required + if (is_jump_dest && current_index%2 == 1) { + Log::printf (Log::Debug, + "Write padding at page %02x, index %03x\n", + current_page, current_index); + writeItem (Item (Item::NoOp), page_data->begin () + current_index); + ++current_index; + } + if (is_jump_dest) + Log::printf (Log::Debug, + "Macro item %p is aligned at %02hhx:%02hhx\n", + &item, + current_page, current_index/2); + + // Write the item itself + Log::printf (Log::Debug, + "Write macro item %p at page %02x, index %03x, op_code is %02hhx\n", + &item, + current_page, current_index, + item.opCode ()); + iterator addr; + writeItem (item, page_data->begin () + current_index, &addr); + + // Remember jump address position for later resolution + if (item.isJump ()) { + jump_addrs.emplace_back (&item, addr); + } + + // Remember item address for later jump resolution + if (is_jump_dest) { + jump_dest->second = Address { + static_cast (current_page), + static_cast (current_index/2) + }; + } + + current_index += Item::getOpLength (item.opCode ()); + } + + // Write jump addresses + for (auto jump_addr: jump_addrs) { + const Item *dest = &*jump_addr.first->jumpDestination (); + Address addr = jump_dests[dest]; + iterator addr_pos = jump_addr.second; + Log::printf (Log::Debug, + "Macro item %p jump to %02hhx:%02hhx\n", + jump_addr.first, + addr.page, addr.offset); + *addr_pos = addr.page; + *(addr_pos+1) = addr.offset; + } + + return Address { + static_cast (current_page), + static_cast ((current_index+1)/2) + }; +} + +void Macro::simplify () +{ + std::map> back_refs; + + for (auto item: _items) { + if (item.isJump ()) { + back_refs[&*item.jumpDestination ()].push_back (&item); + } + } + + + auto it = _items.begin (); + while (it != _items.end ()) { + if (it->opCode () == Item::NoOp || + (it->opCode () == Item::Jump && it->jumpDestination () == std::next(it))) { + for (Item *jump: back_refs[&*it]) { + jump->setJumpDestination (std::next(it)); + } + Log::printf (Log::Debug, + "Remove useless macro item %p: op_code = %02hhx\n", + &*it, it->opCode ()); + it = _items.erase (it); + } + else + ++it; + } +} + +std::list::iterator Macro::begin () +{ + return _items.begin (); +} + +std::list::const_iterator Macro::begin () const +{ + return _items.begin (); +} + +std::list::iterator Macro::end () +{ + return _items.end (); +} + +std::list::const_iterator Macro::end () const +{ + return _items.end (); +} + +const Macro::Item &Macro::back () const +{ + return _items.back (); +} + +Macro::Item &Macro::back () +{ + return _items.back (); +} + +void Macro::emplace_back (uint8_t op_code) +{ + _items.emplace_back (op_code); +} + +bool Macro::isSimple () const +{ + for (auto it = _items.begin (); it != _items.end (); ++it) { + if (!it->isSimple ()) { + if (it->opCode () == Item::End) + return std::next(it) == _items.end (); + return false; + } + } + return false; +} + +bool Macro::isLoop (const_iterator &pre_begin, const_iterator &pre_end, + const_iterator &loop_begin, const_iterator &loop_end, + const_iterator &post_begin, const_iterator &post_end, + unsigned int &loop_delay) const +{ + enum State { + Init, + OptionalLoop, + AfterLoop, + } state = Init; + + pre_begin = _items.begin (); + + for (auto it = _items.begin (); it != _items.end (); ++it) { + if (it->isSimple ()) + continue; + + switch (it->opCode ()) { + case Item::RepeatUntilRelease: + if (state != Init) + return false; + pre_end = pre_begin; + loop_begin = pre_begin; + loop_end = it; + post_begin = std::next (it); + loop_delay = 0; + state = AfterLoop; + break; + + case Item::JumpIfPressed: { + const_iterator dest = it->jumpDestination (); + if (state == Init) { + // Check that the destination is before + // the current instruction. + const_iterator it2; + for (it2 = _items.begin (); it2 != it; ++it2) + if (it2 == dest) + break; + if (it2 == it) // dest not found + return false; + + pre_end = dest; + loop_begin = dest; + loop_end = it; + post_begin = std::next (it); + loop_delay = 0; + state = AfterLoop; + } + else if (state == OptionalLoop) { + loop_end = it; + post_begin = std::next (it); + // Check jump destinations (pre_end is JumpIfReleased) + if (pre_end->jumpDestination () != post_begin || + dest != loop_begin) + return false; + state = AfterLoop; + } + else + return false; + break; + } + + case Item::JumpIfReleased: + if (state != Init) + return false; + pre_end = it; + loop_begin = std::next (it); + loop_delay = it->delay (); + state = OptionalLoop; + break; + + case Item::WaitRelease: + if (state != Init) + return false; + pre_end = it; + loop_begin = loop_end = it; + post_begin = std::next (it); + loop_delay = 0; + state = AfterLoop; + break; + + + case Item::End: + post_end = it; + return state == AfterLoop; + + default: + return false; + } + } + return false; +} + +Macro Macro::buildSimple (const_iterator begin, const_iterator end) +{ + Macro macro; + for (auto it = begin; it != end; ++it) { + if (!it->isSimple ()) + throw std::invalid_argument ("Macro item must be simple"); + macro._items.push_back (*it); + } + macro._items.emplace_back (Item::End); + return macro; +} + +Macro Macro::buildLoop (const_iterator pre_begin, const_iterator pre_end, + const_iterator loop_begin, const_iterator loop_end, + const_iterator post_begin, const_iterator post_end, + unsigned int loop_delay) +{ + // Check inputs + for (auto it = pre_begin; it != pre_end; ++it) { + if (!it->isSimple ()) + throw std::invalid_argument ("Macro item must be simple"); + } + for (auto it = loop_begin; it != loop_end; ++it) { + if (!it->isSimple ()) + throw std::invalid_argument ("Macro item must be simple"); + } + for (auto it = post_begin; it != post_end; ++it) { + if (!it->isSimple ()) + throw std::invalid_argument ("Macro item must be simple"); + } + + Macro macro; + if (loop_begin == loop_end) { + // Inner loop is empty, use wait instruction + macro._items.insert (macro._items.end (), pre_begin, pre_end); + macro._items.emplace_back (Item::WaitRelease); + macro._items.insert (macro._items.end (), post_begin, post_end); + macro._items.emplace_back (Item::End); + } + else if (loop_delay > 0) { + // Use JumpIfReleased to delay the loop + macro._items.insert (macro._items.end (), pre_begin, pre_end); + iterator released_jump = macro._items.emplace (macro._items.end (), Item::JumpIfReleased); + iterator loop = macro._items.insert (macro._items.end (), loop_begin, loop_end); + iterator pressed_jump = macro._items.emplace (macro._items.end (), Item::JumpIfPressed); + iterator post = macro._items.insert (macro._items.end (), post_begin, post_end); + macro._items.emplace_back (Item::End); + + released_jump->setDelay (loop_delay); + released_jump->setJumpDestination (post); + pressed_jump->setJumpDestination (loop); + } + else if (pre_begin == pre_end) { + // No pre-loop instruction, use repeat instruction + macro._items.insert (macro._items.end (), loop_begin, loop_end); + macro._items.emplace_back (Item::RepeatUntilRelease); + macro._items.insert (macro._items.end (), post_begin, post_end); + macro._items.emplace_back (Item::End); + } + else { + // Pre-loop is non-empty, and loop is played at least once + // Use a single JumpIfpressed at the end of loop + macro._items.insert (macro._items.end (), pre_begin, pre_end); + iterator loop = macro._items.insert (macro._items.end (), loop_begin, loop_end); + macro._items.emplace (macro._items.end (), Item::JumpIfPressed)->setJumpDestination (loop); + macro._items.insert (macro._items.end (), post_begin, post_end); + macro._items.emplace_back (Item::End); + } + return macro; +} diff --git a/hidpp/hidpp10/Macro.h b/hidpp/hidpp10/Macro.h new file mode 100755 index 0000000..0eb5997 --- /dev/null +++ b/hidpp/hidpp10/Macro.h @@ -0,0 +1,163 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_MACRO_H +#define HIDPP10_MACRO_H + +#include +#include +#include +#include + +namespace HIDPP10 +{ + +class Macro +{ +public: + class Item + { + public: + Item (uint8_t op_code); + + enum OpCode: uint8_t { + NoOp = 0x00, + WaitRelease = 0x01, + RepeatUntilRelease = 0x02, + RepeatForever = 0x03, + KeyPress = 0x20, + KeyRelease = 0x21, + ModifierPress = 0x22, + ModifierRelease = 0x23, + MouseWheel = 0x24, + MouseButtonPress = 0x40, + MouseButtonRelease = 0x41, + ConsumerControl = 0x42, + Delay = 0x43, + Jump = 0x44, + JumpIfPressed = 0x45, + MousePointer = 0x60, + JumpIfReleased = 0x61, + End = 0xff, + }; + + static std::size_t getOpLength (uint8_t op_code); + + static uint8_t getShortDelayCode (unsigned int delay); + static unsigned int getShortDelayDuration (uint8_t op_code); + + uint8_t opCode () const; + bool isShortDelay () const; + + uint8_t keyCode () const; + void setKeyCode (uint8_t key); + + uint8_t modifiers () const; + void setModifiers (uint8_t modifiers); + + int8_t wheel () const; + void setWheel (int8_t wheel); + + uint16_t buttons () const; + void setButtons (uint16_t buttons); + + uint16_t consumerControl () const; + void setConsumerControl (uint16_t cc); + + unsigned int delay () const; + void setDelay (unsigned int delay); + + bool isJump () const; + std::list::iterator jumpDestination (); + std::list::const_iterator jumpDestination () const; + void setJumpDestination (std::list::iterator); + + int mouseX () const; + int mouseY () const; + void setMouseX (int delta); + void setMouseY (int delta); + + bool isSimple () const; + + private: + uint8_t _op_code; + union { + uint8_t key; + uint8_t modifiers; + int8_t wheel; + uint16_t buttons; + uint16_t cc; + uint16_t delay; + struct { + int16_t x, y; + } mouse; + } _params; + std::list::iterator _dest; + }; + + Macro (); + Macro (MemoryMapping &mem, Address address); + Macro (std::vector::const_iterator begin, Address start_address); + + explicit Macro (const Macro &); + Macro (Macro &&) = default; + + Macro &operator= (const Macro &) = delete; + Macro &operator= (Macro &&) = default; + + std::vector::iterator write (std::vector::iterator begin, Address start_address) const; + Address write (MemoryMapping &mem, Address start) const; + + /** + * Remove no-op and useless unconditional jumps. + */ + void simplify (); + + typedef std::list::iterator iterator; + typedef std::list::const_iterator const_iterator; + + iterator begin (); + const_iterator begin () const; + iterator end (); + const_iterator end () const; + + const Item &back () const; + Item &back (); + + void emplace_back (uint8_t op_code); + + bool isSimple () const; + bool isLoop (const_iterator &pre_begin, const_iterator &pre_end, + const_iterator &loop_begin, const_iterator &loop_end, + const_iterator &post_begin, const_iterator &post_end, + unsigned int &loop_delay) const; + + static Macro buildSimple (const_iterator begin, const_iterator end); + static Macro buildLoop (const_iterator pre_begin, const_iterator pre_end, + const_iterator loop_begin, const_iterator loop_end, + const_iterator post_begin, const_iterator post_end, + unsigned int loop_delay); + +private: + std::list _items; +}; + +} + +#endif + diff --git a/hidpp/hidpp10/MemoryMapping.cpp b/hidpp/hidpp10/MemoryMapping.cpp new file mode 100755 index 0000000..09a4c82 --- /dev/null +++ b/hidpp/hidpp10/MemoryMapping.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include + +#include +#include +#include +#include +#include + +using namespace HIDPP10; + +MemoryMapping::MemoryMapping (Device *dev): + _imem (dev) +{ +} + +const std::vector &MemoryMapping::getReadOnlyPage (unsigned int page) +{ + getPage (page); + return _pages[page].second; +} + +std::vector &MemoryMapping::getWritablePage (unsigned int page) +{ + getPage (page); + _pages[page].first = Modified; + return _pages[page].second; +} + +void MemoryMapping::sync () +{ + // Persistent memory start at page 1 + for (unsigned int i = 1; i < _pages.size (); ++i) { + if (_pages[i].first == Modified) { + uint16_t crc = CRC::CCITT (_pages[i].second.begin (), + _pages[i].second.end () - sizeof (crc)); + Log::printf (Log::Debug, "Page %d CRC is %04hx.\n", i, crc); + writeBE (_pages[i].second, PageSize - sizeof (crc), crc); + _imem.writePage (i, _pages[i].second); + _pages[i].first = Synced; + } + } +} + +void MemoryMapping::getPage (unsigned int page) +{ + if (page >= _pages.size ()) + _pages.resize (page+1, { Absent, std::vector () }); + if (_pages[page].first == Absent) { + _pages[page].second.resize (PageSize); + _imem.readMem ({static_cast (page), 0}, _pages[page].second); + uint16_t crc = CRC::CCITT (_pages[page].second.begin (), + _pages[page].second.end () - sizeof (crc)); + if (crc != readBE (_pages[page].second, PageSize - sizeof (crc))) + Log::warning () << "Invalid CRC for page " << page << std::endl; + _pages[page].first = Synced; + } +} + diff --git a/hidpp/hidpp10/MemoryMapping.h b/hidpp/hidpp10/MemoryMapping.h new file mode 100755 index 0000000..044b96f --- /dev/null +++ b/hidpp/hidpp10/MemoryMapping.h @@ -0,0 +1,63 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_MEMORY_MAPPING_H +#define HIDPP10_MEMORY_MAPPING_H + +#include +#include + +namespace HIDPP10 +{ + +class Device; + +class MemoryMapping +{ +public: + MemoryMapping (Device *dev); + + const std::vector &getReadOnlyPage (unsigned int page); + /** + * Get the page \p page and mark it as "modified". + */ + std::vector &getWritablePage (unsigned int page); + + /** + * Write all modified pages to the device memory except for page 0 + * (page 0 cannot be written as a whole page). + */ + void sync (); + +private: + void getPage (unsigned int page); + + IMemory _imem; + enum PageState + { + Absent, + Synced, + Modified, + }; + std::vector>> _pages; +}; + +} + +#endif + diff --git a/hidpp/hidpp10/Profile.cpp b/hidpp/hidpp10/Profile.cpp new file mode 100755 index 0000000..c7145a9 --- /dev/null +++ b/hidpp/hidpp10/Profile.cpp @@ -0,0 +1,432 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include + +using namespace HIDPP10; + +template +static T clamp (T value, T min, T max) +{ + return std::min (std::max (value, min), max); +} + +std::string Profile::Button::specialFunctionToString (SpecialFunction special) +{ + switch (special) { + case PanLeft: + return "PanLeft"; + case PanRight: + return "PanRight"; + case BatteryLevel: + return "BatteryLevel"; + case NextMode: + return "NextMode"; + case PreviousMode: + return "PreviousMode"; + case CycleMode: + return "CycleMode"; + case NextProfile: + return "NextProfile"; + case CycleProfile: + return "CycleProfile"; + case PreviousProfile: + return "PreviousProfile"; + } + std::stringstream ss; + ss << special; + return ss.str (); +} + +Profile::Button::SpecialFunction Profile::Button::specialFunctionFromString (const std::string &string) +{ + if (string == "PanLeft") + return PanLeft; + if (string == "PanRight") + return PanRight; + if (string == "BatteryLevel") + return BatteryLevel; + if (string == "NextMode") + return NextMode; + if (string == "PreviousMode") + return PreviousMode; + if (string == "CycleMode") + return CycleMode; + if (string == "NextProfile") + return NextProfile; + if (string == "CycleProfile") + return CycleProfile; + if (string == "PreviousProfile") + return PreviousProfile; + std::size_t pos; + unsigned int code = std::stoul (string, &pos, 0); + if (string.c_str ()[pos] != '\0') + throw std::invalid_argument ("Invalid special function"); + return static_cast (code); +} + +Profile::Button::Button (): + _type (Disabled) +{ +} + +void Profile::Button::read (std::vector::const_iterator begin) +{ + switch (*begin) { + case MouseButton: + _type = MouseButton; + _params.button = readLE (begin+1); + break; + + case Key: + _type = Key; + _params.key.modifiers = *(begin+1); + _params.key.code = *(begin+2); + break; + + case Special: + _type = Special; + _params.special = static_cast (readLE (begin+1)); + break; + + case ConsumerControl: + _type = ConsumerControl; + _params.consumer_control = readBE (begin+1); + break; + + case Disabled: + _type = Disabled; + break; + + default: + _type = Macro; + _params.macro.page = *begin; + _params.macro.offset = *(begin+1); + } +} + +void Profile::Button::write (std::vector::iterator begin) const +{ + switch (_type) { + case MouseButton: + *begin = MouseButton; + writeLE (begin+1, _params.button); + break; + + case Key: + *begin = Key; + *(begin+1) = _params.key.modifiers; + *(begin+2) = _params.key.code; + break; + + case Special: + *begin = Special; + writeLE (begin+1, _params.special); + break; + + case ConsumerControl: + *begin = ConsumerControl; + writeBE (begin+1, _params.consumer_control); + break; + + case Disabled: + *begin = Disabled; + break; + + case Macro: + *begin = _params.macro.page; + *(begin+1) = _params.macro.offset; + break; + } +} + +Profile::Button::Type Profile::Button::type () const +{ + return _type; +} + +unsigned int Profile::Button::mouseButton () const +{ + return _params.button; +} + +void Profile::Button::setMouseButton (unsigned int button) +{ + _type = MouseButton; + _params.button = button; +} + +uint8_t Profile::Button::modifierKeys () const +{ + return _params.key.modifiers; +} + +uint8_t Profile::Button::key () const +{ + return _params.key.code; +} + +void Profile::Button::setKey (uint8_t modifiers, uint8_t key_code) +{ + _type = Key; + _params.key.modifiers = modifiers; + _params.key.code = key_code; +} + +Profile::Button::SpecialFunction Profile::Button::special () const +{ + return _params.special; +} + +void Profile::Button::setSpecial (Profile::Button::SpecialFunction special) +{ + _type = Special; + _params.special = special; +} + +uint16_t Profile::Button::consumerControl () const +{ + return _params.consumer_control; +} + +void Profile::Button::setConsumerControl (uint16_t code) +{ + _type = ConsumerControl; + _params.consumer_control = code; +} + +Address Profile::Button::macro () const +{ + return _params.macro; +} + +void Profile::Button::setMacro (Address address) +{ + _type = Macro; + _params.macro = address; +} + +void Profile::Button::disable () +{ + _type = Disabled; +} + + +unsigned int Profile::buttonCount () const +{ + return _buttons.size (); +} + +const Profile::Button &Profile::button (unsigned int index) const +{ + return _buttons[index]; +} + +Profile::Button &Profile::button (unsigned int index) +{ + return _buttons[index]; +} + +Profile::Profile (unsigned int button_count): + _buttons (button_count) +{ +} + +Profile::~Profile () +{ +} + +void Profile::readButtons (std::vector::const_iterator begin) +{ + for (Button &button: _buttons) { + button.read (begin); + begin += 3; + } +} + +void Profile::writeButtons (std::vector::iterator begin) const +{ + for (const Button &button: _buttons) { + button.write (begin); + begin += 3; + } +} + +G500Profile::G500Profile (const Sensor *sensor): + Profile (13), _sensor (sensor), + _color ({ 255, 0, 0 }), + _angle (0x80), + _lift (0), + _unk (0x10), + _poll_interval (8) +{ +} + +G500Profile::~G500Profile () +{ +} + +std::size_t G500Profile::profileLength () const +{ + return 78; +} + +void G500Profile::read (std::vector::const_iterator begin) +{ + _color.r = *(begin+0); + _color.g = *(begin+1); + _color.b = *(begin+2); + _angle = *(begin+3); + _modes.clear (); + for (unsigned int i = 0; i < MaxModeCount; ++i) { + uint16_t x_res = readBE (begin+4+i*6); + if (i > 0 && x_res == 0) // Mode is disabled + break; + uint16_t y_res = readBE (begin+4+i*6+2); + uint16_t leds_raw = readLE (begin+4+i*6+4); + std::vector leds; + for (unsigned int j = 0; j < 4; ++j) { + unsigned int l = (leds_raw >> j*4) & 0x0F; + if (l == 0) + break; + else if (l == 1) + leds.push_back (false); + else if (l == 2) + leds.push_back (true); + else + throw std::runtime_error ("Invalid LED value in G500 profile"); + } + _modes.emplace_back (ResolutionMode ({ + _sensor->toDPI (x_res), + _sensor->toDPI (y_res), + leds })); + } + _angle_snap = *(begin+34) == 0x02; + _default_mode = *(begin+35); + _lift = static_cast (*(begin+36) & 0x1f) - 0x10; + _unk = *(begin+37); + _poll_interval = *(begin+38); + readButtons (begin+39); +} + +void G500Profile::write (std::vector::iterator begin) const +{ + *(begin+0) = _color.r; + *(begin+1) = _color.g; + *(begin+2) = _color.b; + *(begin+3) = _angle; + for (unsigned int i = 0; i < _modes.size (); ++i) { + writeBE (begin+4+i*6, + _sensor->fromDPI (_modes[i].x_res)); + writeBE (begin+4+i*6+2, + _sensor->fromDPI (_modes[i].y_res)); + uint16_t leds = 0; + for (unsigned int j = 0; j < _modes[i].leds.size (); ++j) + leds |= (_modes[i].leds[j] ? 0x02 : 0x01) << (j*4); + writeLE (begin+4+i*6+4, leds); + } + // Disable the mode after the last one + if (_modes.size () < MaxModeCount) { + writeBE (begin+4+_modes.size ()*6, 0); + } + *(begin+34) = (_angle_snap ? 0x02 : 0x01); + *(begin+35) = _default_mode; + *(begin+36) = 0x10 + clamp (_lift, -15, 15); + *(begin+37) = _unk; + *(begin+38) = _poll_interval; + writeButtons (begin+39); +} + +unsigned int G500Profile::modeCount () const +{ + return _modes.size (); +} + +void G500Profile::setModeCount (unsigned int count) +{ + if (count > MaxModeCount) + count = MaxModeCount; + _modes.resize (count); + // TODO: Add default values for added modes +} + +G500Profile::ResolutionMode G500Profile::resolutionMode (unsigned int index) const +{ + return _modes[index]; +} + +void G500Profile::setResolutionMode (unsigned int index, ResolutionMode resolutionMode) +{ + _modes[index] = resolutionMode; +} + +unsigned int G500Profile::defaultMode () const +{ + return _default_mode; +} + +void G500Profile::setDefaultMode (unsigned int index) +{ + _default_mode = index; +} + +Profile::Color G500Profile::color () const +{ + return _color; +} + +void G500Profile::setColor (Color color) +{ + _color = color; +} + +bool G500Profile::angleSnap () const +{ + return _angle_snap; +} + +void G500Profile::setAngleSnap (bool enabled) +{ + _angle_snap = enabled; +} + +int G500Profile::liftThreshold () const +{ + return _lift; +} + +void G500Profile::setLiftThreshold (int lift) +{ + _lift = lift; +} + +unsigned int G500Profile::pollInterval () const +{ + return _poll_interval; +} + +void G500Profile::setPollInterval (unsigned int interval) +{ + _poll_interval = interval; +} + diff --git a/hidpp/hidpp10/Profile.h b/hidpp/hidpp10/Profile.h new file mode 100755 index 0000000..d8489d9 --- /dev/null +++ b/hidpp/hidpp10/Profile.h @@ -0,0 +1,182 @@ +/* + * Copyright 2015 Clément Vuchener + * + * 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 . + * + */ + +#ifndef HIDPP10_PROFILE_H +#define HIDPP10_PROFILE_H + +#include +#include +#include +#include + +namespace HIDPP10 +{ + +class Sensor; + +enum ProfileType { + NoProfile, + G9ProfileType, + G500ProfileType, + G700ProfileType, +}; + +class Profile +{ +public: + struct Color { + uint8_t r, g, b; + }; + + class Button { + public: + enum Type: uint8_t { + Macro, + MouseButton = 0x81, + Key = 0x82, + Special = 0x83, + ConsumerControl = 0x84, + Disabled = 0x8f, + }; + + enum SpecialFunction: uint16_t { + PanLeft = 0x0001, + PanRight = 0x0002, + BatteryLevel = 0x0003, + NextMode = 0x0004, + PreviousMode = 0x0008, + CycleMode = 0x0009, + NextProfile = 0x0010, + CycleProfile = 0x0011, + PreviousProfile = 0x0020, + }; + + static std::string specialFunctionToString (SpecialFunction); + static SpecialFunction specialFunctionFromString (const std::string &); + + Button (); + + void read (std::vector::const_iterator begin); + void write (std::vector::iterator begin) const; + + Type type () const; + + unsigned int mouseButton () const; + void setMouseButton (unsigned int button); + + uint8_t modifierKeys () const; + uint8_t key () const; + void setKey (uint8_t modifiers, uint8_t key_code); + + SpecialFunction special () const; + void setSpecial (SpecialFunction special); + + uint16_t consumerControl () const; + void setConsumerControl (uint16_t code); + + Address macro () const; + void setMacro (Address address); + + void disable (); + + private: + Type _type; + union { + uint16_t button; + struct { + uint8_t modifiers; + uint8_t code; + } key; + SpecialFunction special; + uint16_t consumer_control; + Address macro; + } _params; + }; + + Profile (unsigned int button_count); + virtual ~Profile (); + + virtual std::size_t profileLength () const = 0; + virtual void read (std::vector::const_iterator begin) = 0; + virtual void write (std::vector::iterator begin) const = 0; + + unsigned int buttonCount () const; + const Button &button (unsigned int index) const; + Button &button (unsigned int index); + +protected: + void readButtons (std::vector::const_iterator begin); + void writeButtons (std::vector::iterator begin) const; + +private: + std::vector