diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 052fd29..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "src/logid/hidpp"] - path = src/logid/hidpp - url = https://github.com/PixlOne/hidpp.git - branch = master diff --git a/CMakeLists.txt b/CMakeLists.txt index 22658b3..a1d9fd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,4 +11,41 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -Wall -Wextra") set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) +find_package(Git) + +# Set version number +if(EXISTS ${CMAKE_SOURCE_DIR}/version.txt) + file(READ version.txt LOGIOPS_VERSION) + string(REGEX REPLACE "\n$" "" LOGIOPS_VERSION ${LOGIOPS_VERSION}) +endif() + +if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git) + execute_process(COMMAND ${GIT_EXECUTABLE} + rev-parse --abbrev-ref HEAD + OUTPUT_VARIABLE LOGIOPS_GIT_BRANCH) + string(REGEX REPLACE "\n$" "" LOGIOPS_GIT_BRANCH ${LOGIOPS_GIT_BRANCH}) + if(LOGIOPS_GIT_BRANCH MATCHES "^tags/?") + STRING(REGEX REPLACE "^tags/" "" + LOGIOPS_VERSION ${LOGIOPS_GIT_BRANCH}) + else() + execute_process(COMMAND ${GIT_EXECUTABLE} + rev-parse --short HEAD + OUTPUT_VARIABLE LOGIOPS_COMMIT_HASH) + string(REGEX REPLACE "\n$" "" LOGIOPS_COMMIT_HASH ${LOGIOPS_COMMIT_HASH}) + if(LOGIOPS_VERSION) + string(APPEND LOGIOPS_VERSION -${LOGIOPS_COMMIT_HASH}) + else() + set(LOGIOPS_VERSION git-${LOGIOPS_COMMIT_HASH}) + endif() + endif() +endif() + +if(LOGIOPS_VERSION) + message("LogiOps Version Number: ${LOGIOPS_VERSION}") +else() + set(LOGIOPS_VERSION "null") +endif() + +add_definitions( -DLOGIOPS_VERSION="${LOGIOPS_VERSION}") + add_subdirectory(src/logid) diff --git a/README.md b/README.md index 1a05617..da742d1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Default location for the configuration file is /etc/logid.cfg. ## Dependencies -This project requires a C++14 compiler, cmake, libevdev, libudev, and libconfig. For popular distributions, I've included commands below. +This project requires a C++14 compiler, cmake, libevdev, libudev, and libconfig. For popular distributions, I've included commands below. **Debian/Ubuntu:** `sudo apt install cmake libevdev-dev libudev-dev libconfig++-dev` @@ -42,3 +42,11 @@ I'm also looking for contributors to help in my project; feel free to submit a p ## Compatible Devices [For a list of tested devices, check TESTED.md](TESTED.md) + +## Special Thanks +Thanks to the following people for contributing to this repository. + +- [ClĂ©ment Vuchener & contributors for creating the old HID++ library](https://github.com/cvuchener/hidpp) +- [Developers of Solaar for providing information on HID++](https://github.com/pwr-Solaar/Solaar) +- [Nestor Lopez Casado for providing Logitech documentation on the HID++ protocol](http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28) +- Everyone listed in the contributors page diff --git a/src/logid/Actions.cpp b/src/logid/Actions.cpp deleted file mode 100644 index 747f44c..0000000 --- a/src/logid/Actions.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Actions.h" -#include "util.h" -#include "EvdevDevice.h" - -using namespace logid; - -Gesture::Gesture(ButtonAction* ba, GestureMode m, void* aux) : action (ba), mode (m) -{ - switch(m) - { - case GestureMode::OnFewPixels: - per_pixel = *(int*)aux; - break; - case GestureMode::Axis: - axis = *(axis_info*)aux; - break; - default: - break; - } -} - -NoAction* NoAction::copy(Device *dev) -{ - auto action = new NoAction(); - action->device = dev; - return action; -} -KeyAction* KeyAction::copy(Device *dev) -{ - auto action = new KeyAction(keys); - action->device = dev; - return action; -} -GestureAction* GestureAction::copy(Device* dev) -{ - auto action = new GestureAction({}); - action->device = dev; - for(auto it : gestures) - action->gestures.insert({it.first, new Gesture(*it.second, dev)}); - return action; -} -SmartshiftAction* SmartshiftAction::copy(Device* dev) -{ - auto action = new SmartshiftAction(); - action->device = dev; - return action; -} -HiresScrollAction* HiresScrollAction::copy(Device* dev) -{ - auto action = new HiresScrollAction(); - action->device = dev; - return action; -} -CycleDPIAction* CycleDPIAction::copy(Device* dev) -{ - auto action = new CycleDPIAction(dpis); - action->device = dev; - return action; -} -ChangeDPIAction* ChangeDPIAction::copy(Device* dev) -{ - auto action = new ChangeDPIAction(dpi_inc); - action->device = dev; - return action; -} - -void KeyAction::press() -{ - //KeyPress event for each in keys - for(unsigned int i : keys) - global_evdev->sendEvent(EV_KEY, i, 1); -} - -void KeyAction::release() -{ - //KeyRelease event for each in keys - for(unsigned int i : keys) - global_evdev->sendEvent(EV_KEY, i, 0); -} - -void GestureAction::press() -{ - for(auto g : gestures) - g.second->per_pixel_mod = 0; - - held = true; - x = 0; - y = 0; -} - -void GestureAction::move(HIDPP20::IReprogControlsV4::Move m) -{ - x += m.x; - y += m.y; - if(m.y != 0 && abs(y) > 50) - { - auto g = gestures.find(m.y > 0 ? Direction::Down : Direction::Up); - if(g != gestures.end()) - { - if (g->second->mode == GestureMode::Axis) - global_evdev->moveAxis(g->second->axis.code, abs(m.y) * g->second->axis.multiplier); - if (g->second->mode == GestureMode::OnFewPixels) - { - g->second->per_pixel_mod += abs(m.y); - if(g->second->per_pixel_mod >= g->second->per_pixel) - { - g->second->per_pixel_mod -= g->second->per_pixel; - g->second->action->press(); - g->second->action->release(); - } - } - } - } - if(m.x != 0 && abs(x) > 50) - { - auto g = gestures.find(m.x > 0 ? Direction::Right : Direction::Left); - if(g != gestures.end()) - { - if (g->second->mode == GestureMode::Axis) - global_evdev->moveAxis(g->second->axis.code, abs(m.x) * g->second->axis.multiplier); - if (g->second->mode == GestureMode::OnFewPixels) - { - g->second->per_pixel_mod += abs(m.x); - if (g->second->per_pixel_mod >= g->second->per_pixel) - { - g->second->per_pixel_mod -= g->second->per_pixel; - g->second->action->press(); - g->second->action->release(); - } - } - } - } -} - -void GestureAction::release() -{ - held = false; - Direction direction; - if(abs(x) < 50 && abs(y) < 50) direction = Direction::None; - else direction = getDirection(x, y); - x = 0; - y = 0; - - auto g = gestures.find(direction); - - if(g != gestures.end() && g->second->mode == GestureMode::OnRelease) - { - g->second->action->press(); - g->second->action->release(); - } -} - -void SmartshiftAction::press() -{ - try - { - HIDPP20::ISmartShift iss(device->hidpp_dev); - auto s = iss.getStatus(); - iss.setStatus({new bool(!*s.Active)}); - } - catch(HIDPP20::Error &e) - { - log_printf(ERROR, "Error toggling smart shift, code %d: %s\n", e.errorCode(), e.what()); - } -} - -void HiresScrollAction::press() -{ - try - { - HIDPP20::IHiresScroll ihs(device->hidpp_dev); - auto mode = ihs.getMode(); - mode ^= HIDPP20::IHiresScroll::Mode::HiRes; - ihs.setMode(mode); - } - catch(HIDPP20::Error &e) - { - log_printf(ERROR, "Error toggling hires scroll, code %d: %s\n", e.errorCode(), e.what()); - return; - } -} - -void CycleDPIAction::press() -{ - HIDPP20::IAdjustableDPI iad(device->hidpp_dev); - - try - { - for (uint i = 0; i < iad.getSensorCount(); i++) - { - int current_dpi = std::get<0>(iad.getSensorDPI(i)); - bool found = false; - for (uint j = 0; j < dpis.size(); j++) - { - if (dpis[j] == current_dpi) - { - if (j == dpis.size() - 1) - iad.setSensorDPI(i, dpis[0]); - else - iad.setSensorDPI(i, dpis[j + 1]); - found = true; - break; - } - } - if (found) break; - if (dpis.empty()) iad.setSensorDPI(i, std::get<1>(iad.getSensorDPI(i))); - else iad.setSensorDPI(i, dpis[0]); - } - } - catch(HIDPP20::Error &e) { log_printf(ERROR, "Error while cycling DPI: %s", e.what()); } -} - -void ChangeDPIAction::press() -{ - HIDPP20::IAdjustableDPI iad(device->hidpp_dev); - - try - { - for(uint i = 0; i < iad.getSensorCount(); i++) - { - int current_dpi = std::get<0>(iad.getSensorDPI(i)); - iad.setSensorDPI(i, current_dpi + dpi_inc); - } - } - catch(HIDPP20::Error &e) - { - log_printf(ERROR, "Error while incrementing DPI: %s", e.what()); - } -} \ No newline at end of file diff --git a/src/logid/Actions.h b/src/logid/Actions.h deleted file mode 100644 index a283e3e..0000000 --- a/src/logid/Actions.h +++ /dev/null @@ -1,146 +0,0 @@ -#ifndef LOGID_ACTIONS_H -#define LOGID_ACTIONS_H -#include "Device.h" - -#include -#include -#include - -namespace logid -{ - enum class Action - { - None, - Keypress, - Gestures, - CycleDPI, - ChangeDPI, - ToggleSmartshift, - ToggleHiresScroll - }; - enum class Direction - { - None, - Up, - Down, - Left, - Right - }; - enum class GestureMode - { - NoPress, - OnRelease, - OnFewPixels, - Axis - }; - - class Device; - - class ButtonAction - { - public: - virtual ~ButtonAction() = default; - - Action type; - virtual ButtonAction* copy(Device* dev) = 0; - virtual void press() = 0; - virtual void release() = 0; - // ButtonAction(const ButtonAction &a, Device* d) : type (a.type), device (d) {} - // ButtonAction* create_instance(Device* d); - protected: - ButtonAction(Action a) : type (a) {}; - Device* device; - }; - class NoAction : public ButtonAction - { - public: - NoAction() : ButtonAction(Action::None) {} - virtual NoAction* copy(Device* dev); - virtual void press() {} - virtual void release() {} - }; - class KeyAction : public ButtonAction - { - public: - explicit KeyAction(std::vector k) : ButtonAction(Action::Keypress), keys (std::move(k)) {}; - virtual KeyAction* copy(Device* dev); - virtual void press(); - virtual void release(); - private: - std::vector keys; - }; - class Gesture - { - public: - struct axis_info { - uint code; - float multiplier; - }; - - Gesture(ButtonAction* ba, GestureMode m, void* aux=nullptr); - Gesture(const Gesture &g, Device* dev) - : action (g.action->copy(dev)), mode (g.mode), per_pixel (g.per_pixel), - axis (g.axis) - { - } - - ButtonAction* action; - GestureMode mode; - int per_pixel; - int per_pixel_mod; - axis_info axis; - }; - - class GestureAction : public ButtonAction - { - public: - GestureAction(std::map g) : ButtonAction(Action::Gestures), gestures (std::move(g)) {}; - std::map gestures; - virtual GestureAction* copy(Device* dev); - virtual void press(); - virtual void release(); - void move(HIDPP20::IReprogControlsV4::Move m); - private: - bool held; - int x = 0; - int y = 0; - }; - class SmartshiftAction : public ButtonAction - { - public: - SmartshiftAction() : ButtonAction(Action::ToggleSmartshift) {}; - virtual SmartshiftAction* copy(Device* dev); - virtual void press(); - virtual void release() {} - }; - class HiresScrollAction : public ButtonAction - { - public: - HiresScrollAction() : ButtonAction(Action::ToggleHiresScroll) {}; - virtual HiresScrollAction* copy(Device* dev); - virtual void press(); - virtual void release() {} - }; - class CycleDPIAction : public ButtonAction - { - public: - CycleDPIAction(std::vector d) : ButtonAction(Action::CycleDPI), dpis (d) {}; - virtual CycleDPIAction* copy(Device* dev); - virtual void press(); - virtual void release() {} - private: - const std::vector dpis; - }; - class ChangeDPIAction : public ButtonAction - { - public: - ChangeDPIAction(int i) : ButtonAction(Action::ChangeDPI), dpi_inc (i) {}; - virtual ChangeDPIAction* copy(Device* dev); - virtual void press(); - virtual void release() {} - private: - int dpi_inc; - }; -} - -#endif //LOGID_ACTIONS_H diff --git a/src/logid/CMakeLists.txt b/src/logid/CMakeLists.txt index c924dca..610c109 100644 --- a/src/logid/CMakeLists.txt +++ b/src/logid/CMakeLists.txt @@ -10,69 +10,75 @@ find_package(PkgConfig REQUIRED) add_executable(logid logid.cpp - util.cpp - util.h - Configuration.cpp - Configuration.h - Actions.cpp - Actions.h + util/log.cpp + InputDevice.cpp + DeviceManager.cpp Device.cpp - Device.h - DeviceFinder.cpp - DeviceFinder.h - EvdevDevice.cpp - EvdevDevice.h) + Receiver.cpp + Configuration.cpp + features/DPI.cpp + features/SmartShift.cpp + features/HiresScroll.cpp + features/RemapButton.cpp + actions/Action.cpp + actions/NullAction.cpp + actions/KeypressAction.cpp + actions/ToggleHiresScroll.cpp + actions/ToggleSmartShift.cpp + actions/CycleDPI.cpp + actions/ChangeDPI.cpp + actions/GestureAction.cpp + actions/gesture/Gesture.cpp + actions/gesture/ReleaseGesture.cpp + actions/gesture/IntervalGesture.cpp + actions/gesture/AxisGesture.cpp + actions/gesture/NullGesture.cpp + backend/Error.cpp + backend/raw/DeviceMonitor.cpp + backend/raw/RawDevice.cpp + backend/dj/Receiver.cpp + backend/dj/ReceiverMonitor.cpp + backend/dj/Error.cpp + backend/hidpp/Device.cpp + backend/hidpp/Report.cpp + backend/hidpp10/Error.cpp + backend/hidpp10/Device.cpp + backend/hidpp20/Device.cpp + backend/hidpp20/Error.cpp + backend/hidpp20/Feature.cpp + backend/hidpp20/EssentialFeature.cpp + backend/hidpp20/features/Root.cpp + backend/hidpp20/features/FeatureSet.cpp + backend/hidpp20/features/DeviceName.cpp + backend/hidpp20/features/Reset.cpp + backend/hidpp20/features/AdjustableDPI.cpp + backend/hidpp20/features/SmartShift.cpp + backend/hidpp20/features/ReprogControls.cpp + backend/hidpp20/features/HiresScroll.cpp + backend/dj/Report.cpp + util/mutex_queue.h + util/workqueue.cpp + util/worker_thread.cpp + util/task.cpp + util/thread.cpp + util/ExceptionHandler.cpp) + set_target_properties(logid PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -pkg_check_modules(PC_EVDEV libevdev) +pkg_check_modules(PC_EVDEV libevdev REQUIRED) pkg_check_modules(SYSTEMD "systemd") -pkg_check_modules(LIBCONFIG libconfig) - -find_path(HIDPP_INCLUDE_DIR hidpp) -find_library(HIDPP_LIBRARY libhidpp.so) +pkg_check_modules(LIBCONFIG libconfig REQUIRED) +pkg_check_modules(LIBUDEV libudev REQUIRED) find_path(EVDEV_INCLUDE_DIR libevdev/libevdev.h HINTS ${PC_EVDEV_INCLUDE_DIRS} ${PC_EVDEV_INCLUDEDIR}) find_library(EVDEV_LIBRARY NAMES evdev libevdev) -if((NOT HIDPP_INCLUDE_DIR) OR (NOT EXISTS ${HIDPP_INCLUDE_DIR}) OR (NOT HIDPP_LIBRARY) OR FORCE_BUILD_HIDPP) - message("Could not find libhidpp include dir, getting submodule") +include_directories(${EVDEV_INCLUDE_DIR} ${LIBUDEV_INCLUDE_DIRECTORIES}) - execute_process(COMMAND git submodule update --init -- hidpp - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - - if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") - set(DEFAULT_HID_BACKEND "linux") - elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - set(DEFAULT_HID_BACKEND "windows") - else() - message(WARNING "System is not supported") - endif() - - set(HID_BACKEND "${DEFAULT_HID_BACKEND}" CACHE STRING "Backend used for accessing HID devices") - set_property(CACHE HID_BACKEND PROPERTY STRINGS linux windows) - - if("${HID_BACKEND}" STREQUAL "linux") - pkg_check_modules(LIBUDEV libudev REQUIRED) - elseif("${HID_BACKEND}" STREQUAL "windows") - add_definitions(-DUNICODE -D_UNICODE) - add_definitions(-D_WIN32_WINNT=0x0600) # Use vista or later - else() - message(FATAL_ERROR "HID_BACKEND is invalid.") - endif() - - add_subdirectory(hidpp/src/libhidpp) - - set(HIDPP_INCLUDE_DIR "hidpp/src/libhidpp/") - set(HIDPP_LIBRARY hidpp) -else() - set(HIDPP_INCLUDE_DIR ${HIDPP_INCLUDE_DIR}/hidpp) -endif() - -include_directories(${HIDPP_INCLUDE_DIR} ${EVDEV_INCLUDE_DIR}) - -target_link_libraries(logid ${CMAKE_THREAD_LIBS_INIT} ${EVDEV_LIBRARY} config++ ${HIDPP_LIBRARY}) +target_link_libraries(logid ${CMAKE_THREAD_LIBS_INIT} ${EVDEV_LIBRARY} config++ + ${LIBUDEV_LIBRARIES}) install(TARGETS logid DESTINATION bin) diff --git a/src/logid/Configuration.cpp b/src/logid/Configuration.cpp index 9af319e..a5fad09 100644 --- a/src/logid/Configuration.cpp +++ b/src/logid/Configuration.cpp @@ -1,518 +1,178 @@ -#include +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include #include #include -#include -#include -#include -#include -#include -#include #include "Configuration.h" -#include "util.h" +#include "util/log.h" using namespace logid; using namespace libconfig; +using namespace std::chrono; -Configuration::Configuration(const char *config_file) +Configuration::Configuration(const std::string& config_file) { - //Read config file - try - { - cfg.readFile(config_file); - } - catch(const FileIOException &e) - { - log_printf(ERROR, "I/O Error while reading %s: %s", config_file, e.what()); + try { + _config.readFile(config_file.c_str()); + } catch(const FileIOException &e) { + logPrintf(ERROR, "I/O Error while reading %s: %s", config_file.c_str(), + e.what()); + throw e; + } catch(const ParseException &e) { + logPrintf(ERROR, "Parse error in %s, line %d: %s", e.getFile(), + e.getLine(), e.getError()); throw e; } - catch(const ParseException &e) - { - log_printf(ERROR, "Parse error in %s, line %d: %s", e.getFile(), e.getLine(), e.getError()); - throw e; - } - const Setting &root = cfg.getRoot(); - try - { - auto& _blacklist = root.lookup("blacklist"); - if(_blacklist.isArray() || _blacklist.isList()) - { - int len = _blacklist.getLength(); - for(int i = 0; i < len; i++) - { - if(!_blacklist[i].isNumber()) { - log_printf(WARN, "Line %d: blacklist must only contain " - "PIDs", _blacklist[i].getSourceLine()); - if(_blacklist.isArray()) + const Setting &root = _config.getRoot(); + + try { + auto& worker_count = root["workers"]; + if(worker_count.getType() == Setting::TypeInt) { + _worker_threads = worker_count; + if(_worker_threads < 0) + logPrintf(WARN, "Line %d: workers cannot be negative.", + worker_count.getSourceLine()); + } else { + logPrintf(WARN, "Line %d: workers must be an integer.", + worker_count.getSourceLine()); + } + } catch(const SettingNotFoundException& e) { + // Ignore + } + + try { + auto& timeout = root["io_timeout"]; + if(timeout.isNumber()) { + if(timeout.getType() == Setting::TypeFloat) + _io_timeout = duration_cast( + duration(timeout)); + else + _io_timeout = milliseconds((int)timeout); + } else + logPrintf(WARN, "Line %d: io_timeout must be a number.", + timeout.getSourceLine()); + } catch(const SettingNotFoundException& e) { + // Ignore + } + + try { + auto& devices = root["devices"]; + + for(int i = 0; i < devices.getLength(); i++) { + const Setting& device = devices[i]; + std::string name; + try { + if(!device.lookupValue("name", name)) { + logPrintf(WARN, "Line %d: 'name' must be a string, skipping" + " device.", device["name"].getSourceLine()); + continue; + } + } catch(SettingNotFoundException &e) { + logPrintf(WARN, "Line %d: Missing name field, skipping device." + , device.getSourceLine()); + continue; + } + _device_paths.insert({name, device.getPath()}); + } + } + catch(const SettingNotFoundException &e) { + logPrintf(WARN, "No devices listed in config file."); + } + + try { + auto& ignore = root.lookup("ignore"); + if(ignore.getType() == libconfig::Setting::TypeInt) { + _ignore_list.insert((int)ignore); + } else if(ignore.isList() || ignore.isArray()) { + int ignore_count = ignore.getLength(); + for(int i = 0; i < ignore_count; i++) { + if(ignore[i].getType() != libconfig::Setting::TypeInt) { + logPrintf(WARN, "Line %d: ignore must refer to device PIDs", + ignore[i].getSourceLine()); + if(ignore.isArray()) break; - if(_blacklist.isList()) - continue; + } else + _ignore_list.insert((int)ignore[i]); + } + } + } catch(const SettingNotFoundException& e) { + // May be called blacklist + try { + auto& ignore = root.lookup("blacklist"); + if(ignore.getType() == libconfig::Setting::TypeInt) { + _ignore_list.insert((int)ignore); + } else if(ignore.isList() || ignore.isArray()) { + int ignore_count = ignore.getLength(); + for(int i = 0; i < ignore_count; i++) { + if(ignore[i].getType() != libconfig::Setting::TypeInt) { + logPrintf(WARN, "Line %d: blacklist must refer to " + "device PIDs", + ignore[i].getSourceLine()); + if(ignore.isArray()) + break; + } else + _ignore_list.insert((int)ignore[i]); } - blacklist.push_back((int)_blacklist[i]); } + } catch(const SettingNotFoundException& e) { + // Ignore } - else - { - log_printf(WARN, "Line %d: blacklist must be an array or list, " - "ignnoring.", _blacklist.getSourceLine()); - } - } - catch(const SettingNotFoundException &e) - { - } - - Setting* _devices; - - try { _devices = &root["devices"]; } - catch(const SettingNotFoundException &e) - { - log_printf(WARN, "No devices listed in config file."); - return; - } - - for(int i = 0; i < _devices->getLength(); i++) - { - const Setting &device = (*_devices)[i]; - std::string name; - try - { - if(!device.lookupValue("name", name)) - { - log_printf(WARN, "Line %d: 'name' must be a string, skipping device.", device["name"].getSourceLine()); - continue; - } - } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "Line %d: Missing 'name' field, skipping device.", device.getSourceLine()); - continue; - } - devices.insert({name, new DeviceConfig(device)}); } } -DeviceConfig::DeviceConfig(const libconfig::Setting &root) +libconfig::Setting& Configuration::getSetting(const std::string& path) { - try - { - int d; - if(!root.lookupValue("dpi", d)) - throw SettingTypeException(root["dpi"]); - dpi = new int(d); - } - catch(const SettingNotFoundException &e) { } - catch(const SettingTypeException &e) - { - log_printf(WARN, "Line %d: DPI must me an integer; not setting.", root["dpi"].getSourceLine()); - } - - try - { - const Setting& hr = root["hiresscroll"]; - uint8_t hss = 0; - try - { - bool b; - if(!hr.lookupValue("hires", b)) - throw SettingTypeException(root["hires"]); - if(b) hss |= HIDPP20::IHiresScroll::HiRes; - } - catch(SettingNotFoundException &e) { } - catch(SettingTypeException &e) - { - log_printf(INFO, "Line %d: hires field must be a boolean", hr["hires"].getSourceLine()); - } - - try - { - bool b; - if(!hr.lookupValue("invert", b)) - throw SettingTypeException(root["invert"]); - if(b) hss |= HIDPP20::IHiresScroll::Inverted; - } - catch(SettingNotFoundException &e) { } - catch(SettingTypeException &e) - { - log_printf(INFO, "Line %d: invert field must be a boolean", hr["invert"].getSourceLine()); - } - - try - { - bool b; - if(!hr.lookupValue("target", b)) - throw SettingTypeException(root["target"]); - if(b) hss |= HIDPP20::IHiresScroll::Target; - } - catch(SettingNotFoundException &e) { } - catch(SettingTypeException &e) - { - log_printf(INFO, "Line %d: target field must be a boolean", hr["target"].getSourceLine()); - } - - hiresscroll = new uint8_t(hss); - } - catch(const SettingNotFoundException &e) - { - log_printf(INFO, "Missing hiresscroll option, not setting."); - } - catch(const SettingTypeException &e) - { - log_printf(WARN, "Line %d: hiresscroll should be an object", root["hiresscroll"].getSourceLine()); - } - - try - { - const Setting& ss = root["smartshift"]; - smartshift = new HIDPP20::ISmartShift::SmartshiftStatus {}; - bool on; - int threshold; - try - { - if (ss.lookupValue("on", on)) smartshift->Active = new bool(on); - else log_printf(WARN, "Line %d: on field must be a boolean", ss["on"].getSourceLine()); - } - catch(const SettingNotFoundException &e) { } - - try - { - if (ss.lookupValue("threshold", threshold)) - { - if(threshold < 0) - { - threshold = 1; - log_printf(INFO, "Smartshift threshold must be > 0 or < 100, setting to 1."); - } - if(threshold >= 100) - { - threshold = 99; - log_printf(INFO, "Smartshift threshold must be > 0 or < 100, setting to 99."); - } - smartshift->AutoDisengage = new uint8_t(threshold); - smartshift->DefaultAutoDisengage = new uint8_t(threshold); - } - else log_printf(WARN, "Line %d: threshold must be an integer", ss["threshold"].getSourceLine()); - } - catch(const SettingNotFoundException &e) { } - } - catch(const SettingNotFoundException &e) { } - catch(const SettingTypeException &e) - { - log_printf(WARN, "Line %d: smartshift field must be an object", root["smartshift"].getSourceLine()); - } - - Setting* buttons; - try - { - buttons = &root["buttons"]; - } - catch(const SettingNotFoundException &e) - { - log_printf(WARN, "No button configuration found, reverting to null config."); - new std::map(); - return; - } - - for(int i = 0; i < buttons->getLength(); i++) - { - const Setting &button = (*buttons)[i]; - - int cid; - try { button.lookupValue("cid", cid); } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "Entry on line %d is missing a cid", button.getSourceLine()); - continue; - } - - if(actions.find(cid) != actions.end()) - { - log_printf(WARN, "Duplicate entries for cid 0x%x, skipping entry on line %d", cid, button.getSourceLine()); - continue; - } - - Setting* action_config; - try { action_config = &button["action"]; } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "cid 0x%x is missing an action, not diverting!", cid); - continue; - } - - Action action_type; - try - { - std::string action_type_str; - action_config->lookupValue("type", action_type_str); - action_type = stringToAction(action_type_str); - } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "cid 0x%x is missing an action type, not diverting!", cid); - continue; - } - catch(std::invalid_argument &e) - { - log_printf(WARN, "Line %d: %s", (*action_config)["type"].getSourceLine(), e.what()); - continue; - } - - try { actions.insert({cid, parse_action(action_type, action_config)}); } - catch(std::exception &e) { log_printf(ERROR, "%s", e.what()); } - } + return _config.lookup(path); } -ButtonAction* logid::parse_action(Action type, const Setting* action_config, bool is_gesture) +std::string Configuration::getDevice(const std::string& name) { - if(type == Action::None) return new NoAction(); - if(type == Action::Keypress) - { - std::vector keys; - try - { - const Setting &keys_config = (*action_config)["keys"]; - for (int i = 0; i < keys_config.getLength(); i++) - { - int keycode = libevdev_event_code_from_name(EV_KEY, keys_config[i]); - if(keycode == -1) - { - const char* keyname = keys_config[i]; - log_printf(WARN, "%s is not a valid keycode, skipping", keyname); - } - else keys.push_back(keycode); - } - } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "Expected keys parameter on line %d", action_config->getSourceLine()); - } - - return new KeyAction(keys); - } - else if(type == Action::Gestures) - { - if(is_gesture) - { - log_printf(WARN, "Line %d: Recursive gesture, defaulting to no action.", action_config->getSourceLine()); - return new NoAction(); - } - std::map gestures; - - Setting* gestures_config; - - try { gestures_config = &(*action_config)["gestures"]; } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "No gestures parameter for line %d, skipping", action_config->getSourceLine()); - throw e; - } - - for(int i = 0; i < gestures_config->getLength(); i++) - { - const Setting &gesture_config = (*gestures_config)[i]; - - std::string direction_str; - Direction direction; - try - { - gesture_config.lookupValue("direction", direction_str); - direction = stringToDirection(direction_str); - } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "No direction set on line %d", gesture_config.getSourceLine()); - continue; - } - catch(std::invalid_argument &e) - { - log_printf(WARN, "Line %d: %s", gesture_config["direction"].getSourceLine(), e.what()); - continue; - } - - if(gestures.find(direction) != gestures.end()) - { - log_printf(WARN, "Entry on line %d is a duplicate, skipping...", gesture_config["direction"].getSourceLine()); - continue; - } - - GestureMode mode; - try - { - std::string mode_str; - gesture_config.lookupValue("mode", mode_str); - mode = stringToGestureMode(mode_str); - } - catch (SettingNotFoundException &e) - { - log_printf(INFO, "Gesture mode on line %d not found, defaulting to OnRelease", gesture_config.getSourceLine()); - mode = GestureMode::OnRelease; - } - - if(mode == GestureMode::NoPress) - { - gestures.insert({direction, new Gesture(new NoAction(), mode)}); - continue; - } - - if(mode == GestureMode::Axis) - { - Gesture::axis_info axis; - try - { - std::string axis_str; - if(!gesture_config.lookupValue("axis", axis_str)) - throw SettingTypeException(gesture_config["axis"]); - axis.code = libevdev_event_code_from_name(EV_REL, axis_str.c_str()); - } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "Line %d: No axis found, defaulting to no action.", gesture_config.getSourceLine()); - gestures.insert({direction, new Gesture(new NoAction(), GestureMode::NoPress)}); - continue; - } - catch(SettingTypeException &e) - { - log_printf(WARN, "Line %d: Axis must be a string (e.g. 'REL_WHEEL')", gesture_config["axis"].getSourceLine()); - gestures.insert({direction, new Gesture(new NoAction(), GestureMode::NoPress)}); - continue; - } - - axis.multiplier = 1; - try - { - if(!gesture_config.lookupValue("axis_multiplier", axis.multiplier)) - { - int im = 1; - if(!gesture_config.lookupValue("axis_multiplier", im)) - throw SettingTypeException(gesture_config["axis_multiplier"]); - axis.multiplier = (float)im; - } - } - catch(SettingNotFoundException &e) { } - catch(SettingTypeException &e) - { - log_printf(WARN, "Line %d: axis_multiplier must be a float/integer", gesture_config["axis_multiplier"].getSourceLine()); - continue; - } - - gestures.insert({direction, new Gesture(new NoAction(), GestureMode::Axis, &axis)}); - continue; - } - - Setting* g_action; - try { g_action = &gesture_config["action"]; } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "No action set for %s", direction_str.c_str()); - continue; - } - - Action g_type; - try - { - std::string type_str; - g_action->lookupValue("type", type_str); - g_type = stringToAction(type_str); - } - catch(SettingNotFoundException &e) - { - log_printf(INFO, "Missing an action type on line %d, skipping", g_action->getSourceLine()); - continue; - } - catch(std::invalid_argument &e) - { - log_printf(WARN, "Line %d: %s", (*g_action)["type"].getSourceLine(), e.what()); - continue; - } - - ButtonAction* ba; - - try { ba = parse_action(g_type, g_action, true); } - catch(std::exception &e) { continue; } - - if(mode == GestureMode::OnFewPixels) - { - try - { - int pp; - if(!gesture_config.lookupValue("pixels", pp)) - throw SettingTypeException(gesture_config["pixels"]); - gestures.insert({direction, new Gesture(ba, mode, &pp)}); - } - catch(SettingNotFoundException &e) - { - log_printf(WARN, "Line %d: OnFewPixels requires a 'pixels' field.", gesture_config.getSourceLine()); - } - catch(SettingTypeException &e) - { - log_printf(WARN, "Line %d: pixels must be an integer", gesture_config["pixels"].getSourceLine()); - continue; - } - } - else gestures.insert({direction, new Gesture(ba, mode)}); - } - - return new GestureAction(gestures); - } - else if(type == Action::ToggleSmartshift) return new SmartshiftAction(); - else if(type == Action::ToggleHiresScroll) return new HiresScrollAction(); - else if(type == Action::CycleDPI) - { - std::vector dpis; - try - { - const Setting &keys_config = (*action_config)["dpis"]; - for (int i = 0; i < keys_config.getLength(); i++) - { - dpis.push_back((int)keys_config[i]); - } - } - catch(SettingNotFoundException &e) - { - log_printf(ERROR, "Line %d: CycleDPI action is missing 'dpis' field, defaulting to NoAction.", action_config->getSourceLine()); - } - - return new CycleDPIAction(dpis); - } - else if(type == Action::ChangeDPI) - { - int inc; - try - { - action_config->lookupValue("inc", inc); - } - catch(SettingNotFoundException &e) - { - log_printf(ERROR, "Line %d: ChangeDPI action is missing an 'inc' field, defaulting to NoAction.",action_config->getSourceLine()); - return new NoAction(); - } - - return new ChangeDPIAction(inc); - } - - log_printf(ERROR, "This shouldn't have happened. Unhandled action type? Defaulting to NoAction"); - return new NoAction(); + auto it = _device_paths.find(name); + if(it == _device_paths.end()) + throw DeviceNotFound(name); + else + return it->second; } -DeviceConfig::DeviceConfig(DeviceConfig* dc, Device* dev) : baseConfig (false) +bool Configuration::isIgnored(uint16_t pid) const { - dpi = dc->dpi; - smartshift = dc->smartshift; - hiresscroll = dc->hiresscroll; - for(auto it : dc->actions) - actions.insert( { it.first, it.second->copy(dev) } ); + return _ignore_list.find(pid) != _ignore_list.end(); } -DeviceConfig::DeviceConfig() +Configuration::DeviceNotFound::DeviceNotFound(std::string name) : + _name (std::move(name)) { - dpi = nullptr; - hiresscroll = nullptr; - smartshift = nullptr; - actions = {}; } -DeviceConfig::~DeviceConfig() +const char * Configuration::DeviceNotFound::what() const noexcept { - for(auto it : this->actions) - delete(it.second); + return _name.c_str(); +} + +int Configuration::workerCount() const +{ + return _worker_threads; +} + +std::chrono::milliseconds Configuration::ioTimeout() const +{ + return _io_timeout; } diff --git a/src/logid/Configuration.h b/src/logid/Configuration.h index 7ab0868..03eb62d 100644 --- a/src/logid/Configuration.h +++ b/src/logid/Configuration.h @@ -1,45 +1,64 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + #ifndef LOGID_CONFIGURATION_H #define LOGID_CONFIGURATION_H #include #include -#include -#include "Actions.h" +#include +#include +#include + +#define LOGID_DEFAULT_IO_TIMEOUT std::chrono::seconds(2) +#define LOGID_DEFAULT_WORKER_COUNT 4 namespace logid { - class DeviceConfig; - class ButtonAction; - enum class Action; - - class DeviceConfig - { - public: - DeviceConfig(); - ~DeviceConfig(); - DeviceConfig(DeviceConfig* dc, Device* dev); - DeviceConfig(const libconfig::Setting& root); - const int* dpi = nullptr; - HIDPP20::ISmartShift::SmartshiftStatus* smartshift = nullptr; - const uint8_t* hiresscroll = nullptr; - std::map actions; - const bool baseConfig = true; - }; - class Configuration { public: - Configuration(const char* config_file); - Configuration() {} - std::map devices; - std::vector blacklist; + explicit Configuration(const std::string& config_file); + Configuration() = default; + libconfig::Setting& getSetting(const std::string& path); + std::string getDevice(const std::string& name); + bool isIgnored(uint16_t pid) const; + + class DeviceNotFound : public std::exception + { + public: + explicit DeviceNotFound(std::string name); + const char* what() const noexcept override; + private: + std::string _name; + }; + + std::chrono::milliseconds ioTimeout() const; + int workerCount() const; private: - libconfig::Config cfg; + std::map _device_paths; + std::set _ignore_list; + std::chrono::milliseconds _io_timeout = LOGID_DEFAULT_IO_TIMEOUT; + int _worker_threads = LOGID_DEFAULT_WORKER_COUNT; + libconfig::Config _config; }; - ButtonAction* parse_action(Action action, const libconfig::Setting* action_config, bool is_gesture=false); - - extern Configuration* global_config; + extern std::shared_ptr global_config; } -#endif //LOGID_CONFIGURATION_H \ No newline at end of file +#endif //LOGID_CONFIGURATION_H diff --git a/src/logid/Device.cpp b/src/logid/Device.cpp index 215a833..9d32588 100644 --- a/src/logid/Device.cpp +++ b/src/logid/Device.cpp @@ -1,551 +1,139 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "util/log.h" +#include "features/DPI.h" #include "Device.h" -#include "util.h" -#include "EvdevDevice.h" +#include "features/SmartShift.h" +#include "features/RemapButton.h" +#include "backend/hidpp20/features/Reset.h" +#include "features/HiresScroll.h" using namespace logid; +using namespace logid::backend; -using namespace std::chrono_literals; - -Device::Device(std::string p, const HIDPP::DeviceIndex i) : path(std::move(p)), index (i) +Device::Device(std::string path, backend::hidpp::DeviceIndex index) : + _hidpp20 (path, index), _path (std::move(path)), _index (index), + _config (global_config, this) { - disconnected = true; - dispatcher = new HIDPP::SimpleDispatcher(path.c_str()); - listener = new SimpleListener(new HIDPP::SimpleDispatcher(path.c_str()), index); - config = new DeviceConfig(); + _init(); } -bool Device::init() +Device::Device(const std::shared_ptr& raw_device, + hidpp::DeviceIndex index) : _hidpp20(raw_device, index), _path + (raw_device->hidrawPath()), _index (index), + _config (global_config, this) { - // Initialise variables - disconnected = false; - try - { - hidpp_dev = new HIDPP20::Device(dispatcher, index); - } - catch(HIDPP10::Error &e) { return false; } - catch(HIDPP20::Error &e) { return false; } - - name = hidpp_dev->name(); - - if(std::find(global_config->blacklist.begin(), global_config->blacklist.end(), - hidpp_dev->productID()) != global_config->blacklist.end()) - { - log_printf(INFO, "Ignored blacklisted device %s", name.c_str()); - throw BlacklistedDevice(); - } - - features = getFeatures(); - // Set config, if none is found for this device then use default - if(global_config->devices.find(name) == global_config->devices.end()) - log_printf(INFO, "Device %s not configured, using default config.", hidpp_dev->name().c_str()); - else - { - delete(config); - config = global_config->devices.find(name)->second; - } - - initialized = true; - - return true; + _init(); } -Device::~Device() +void Device::_init() { - if(!disconnected) - this->reset(); - if(!config->baseConfig) - delete(this->config); + logPrintf(INFO, "Device found: %s on %s:%d", name().c_str(), + hidpp20().devicePath().c_str(), _index); + + _addFeature("dpi"); + _addFeature("smartshift"); + _addFeature("hiresscroll"); + _addFeature("remapbutton"); + + _makeResetMechanism(); + reset(); + + for(auto& feature: _features) { + feature.second->configure(); + feature.second->listen(); + } + + _hidpp20.listen(); } -void Device::configure() +std::string Device::name() { - if(config->baseConfig) - config = new DeviceConfig(config, this); + return _hidpp20.name(); +} - if(!configuring.try_lock()) - { - log_printf(DEBUG, "%s %d: skip config task", path.c_str(), index); - return; - } - usleep(500000); +uint16_t Device::pid() +{ + return _hidpp20.pid(); +} - try - { - if(disconnected) - goto ret; - // Divert buttons - divert_buttons(); +void Device::sleep() +{ + logPrintf(INFO, "%s:%d fell asleep.", _path.c_str(), _index); +} - if(disconnected) - goto ret; - // Set DPI if it is configured - if(config->dpi != nullptr) - setDPI(*config->dpi); +void Device::wakeup() +{ + logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); - if(disconnected) - goto ret; - // Set Smartshift if it is configured - if(config->smartshift != nullptr) - setSmartShift(*config->smartshift); + reset(); - if(disconnected) - goto ret; - // Set Hires Scroll if it is configured - if(config->hiresscroll != nullptr) - setHiresScroll(*config->hiresscroll); - } - catch(HIDPP10::Error &e) - { - log_printf(ERROR, "HID++ 1.0 Error whjle configuring %s: %s", name.c_str(), e.what()); - } - -ret: - configuring.unlock(); + for(auto& feature: _features) + feature.second->configure(); } void Device::reset() { - try - { - HIDPP20::IReset iReset(hidpp_dev); - iReset.reset(); - } - catch(HIDPP20::UnsupportedFeature &e) { } - catch(HIDPP10::Error &e) - { - log_printf(ERROR, "Failed to reset %s: %s", name.c_str(), e.what()); - } + if(_reset_mechanism) + (*_reset_mechanism)(); + else + logPrintf(DEBUG, "%s:%d tried to reset, but no reset mechanism was " + "available.", _path.c_str(), _index); } -void Device::divert_buttons() +DeviceConfig& Device::config() { - try - { - HIDPP20::IReprogControls irc = HIDPP20::IReprogControls::auto_version(hidpp_dev); - if(disconnected) - return; - int controlCount = irc.getControlCount(); - for(int i = 0; i < controlCount; i++) - { - if(disconnected) - return; - uint16_t cid = irc.getControlInfo(i).control_id; - uint8_t flags = 0; - flags |= HIDPP20::IReprogControls::ChangeTemporaryDivert; - flags |= HIDPP20::IReprogControls::ChangeRawXYDivert; - - auto action = config->actions.find(cid); - if(action != config->actions.end()) - { - flags |= HIDPP20::IReprogControls::ChangeTemporaryDivert; - flags |= HIDPP20::IReprogControls::TemporaryDiverted; - if(action->second->type == Action::Gestures) - flags |= HIDPP20::IReprogControls::RawXYDiverted; - } - if(disconnected) - return; - irc.setControlReporting(cid, flags, cid); - } - } - catch(HIDPP20::UnsupportedFeature &e) - { - log_printf(DEBUG, "%s does not support Reprog controls, not diverting!", name.c_str()); - } - catch(HIDPP20::Error &e) - { - if(e.errorCode() == HIDPP20::Error::InvalidFunctionID) { - // Not really an error, the device does not support diverting buttons - } - else { - log_printf(ERROR, "Could not divert buttons: HID++ 2.0 Error %s!", e.what()); - } - } - catch(HIDPP10::Error &e) - { - log_printf(DEBUG, "Could not divert buttons: HID++ 1.0 Error %s!", e.what()); - } + return _config; } -void Device::setSmartShift(HIDPP20::ISmartShift::SmartshiftStatus ops) +hidpp20::Device& Device::hidpp20() { - try - { - if(disconnected) return; - HIDPP20::ISmartShift ss(hidpp_dev); - if(disconnected) return; - ss.setStatus(ops); - } - catch (HIDPP20::UnsupportedFeature &e) - { - log_printf(ERROR, "Device does not support SmartShift"); - } - catch (HIDPP20::Error &e) - { - log_printf(ERROR, "Error setting SmartShift options, code %d: %s\n", e.errorCode(), e.what()); + return _hidpp20; +} + +void Device::_makeResetMechanism() +{ + try { + hidpp20::Reset reset(&_hidpp20); + _reset_mechanism = std::make_unique>( + [dev=&this->_hidpp20]{ + hidpp20::Reset reset(dev); + reset.reset(reset.getProfile()); + }); + } catch(hidpp20::UnsupportedFeature& e) { + // Reset unsupported, ignore. } } -void Device::setHiresScroll(uint8_t ops) +DeviceConfig::DeviceConfig(const std::shared_ptr& config, Device* + device) : _device (device), _config (config) { - try - { - if(disconnected) return; - HIDPP20::IHiresScroll hs(hidpp_dev); - if(disconnected) return; - hs.setMode(ops); - } - catch (HIDPP20::UnsupportedFeature &e) - { - log_printf(ERROR, "Device does not support Hires Scrolling"); - } - catch (HIDPP20::Error &e) - { - log_printf(ERROR, "Error setting Hires Scrolling options, code %d: %s\n", e.errorCode(), e.what()); + try { + _root_setting = config->getDevice(device->name()); + } catch(Configuration::DeviceNotFound& e) { + logPrintf(INFO, "Device %s not configured, using default config.", + device->name().c_str()); } } -void Device::setDPI(int dpi) +libconfig::Setting& DeviceConfig::getSetting(const std::string& path) { - if(disconnected) return; - HIDPP20::IAdjustableDPI iad(hidpp_dev); - if(disconnected) return; - try { for(unsigned int i = 0; i < iad.getSensorCount(); i++) iad.setSensorDPI(i, dpi); } - catch (HIDPP20::Error &e) { log_printf(ERROR, "Error while setting DPI: %s", e.what()); } -} - -void Device::waitForReceiver() -{ - while(true) - { - waiting_for_receiver = true; - listener->addEventHandler(std::make_unique(this)); - listener->start(); - // Listener stopped, check if stopped or ReceiverHandler event - if (waiting_for_receiver) - return; - - usleep(200000); - - try - { - if(this->init()) break; - } - catch(BlacklistedDevice& e) { return; } - - log_printf(ERROR, "Failed to initialize device %d on %s, waiting for receiver"); - delete(listener); - listener = new SimpleListener(new HIDPP::SimpleDispatcher(path.c_str()), index); - } - log_printf(INFO, "%s detected: device %d on %s", name.c_str(), index, path.c_str()); - this->start(); -} - -void Device::printCIDs() { - try - { - HIDPP20::IReprogControls irc = HIDPP20::IReprogControls::auto_version(hidpp_dev); - if(disconnected) - return; - int controlCount = irc.getControlCount(); - for(int i = 0; i < controlCount; i++) - { - if(disconnected) - return; - uint16_t cid = irc.getControlInfo(i).control_id; - log_printf(DEBUG, "Available CID: 0x%x", cid); - } - } - catch(HIDPP20::UnsupportedFeature &e) - { - log_printf(DEBUG, "%s does not support Reprog controls, not diverting!", name.c_str()); - } -} - -void Device::start() -{ - printCIDs(); - configure(); - try { listener->addEventHandler(std::make_unique(this)); } - catch(HIDPP20::UnsupportedFeature &e) { } - - if(index == HIDPP::DefaultDevice || index == HIDPP::CordedDevice) - { - try { listener->addEventHandler( std::make_unique(this) ); } - catch(HIDPP20::UnsupportedFeature &e) { } - } - listener->start(); -} - -bool Device::testConnection() -{ - int i = MAX_CONNECTION_TRIES; - do { - try - { - HIDPP20::Device _hpp20dev(dispatcher, index); - return true; - } - catch(HIDPP10::Error &e) - { - if(e.errorCode() == HIDPP10::Error::ResourceError) // Asleep, wait for next event - return false; - if(i == MAX_CONNECTION_TRIES-1) - return false; - } - catch(std::exception &e) - { - if(i == MAX_CONNECTION_TRIES-1) - return false; - } - i++; - } while(i < MAX_CONNECTION_TRIES); - - return false; -} - -void ButtonHandler::handleEvent (const HIDPP::Report &event) -{ - switch (event.function()) - { - case HIDPP20::IReprogControls::Event::DivertedButtonEvent: - { - new_states = HIDPP20::IReprogControls::divertedButtonEvent(event); - if (states.empty()) - { - for (uint16_t i : new_states) - std::thread{[=]() { dev->pressButton(i); }}.detach(); - states = new_states; - break; - } - std::vector::iterator it; - std::vector cids(states.size() + new_states.size()); - it = std::set_union(states.begin(), states.end(), new_states.begin(), new_states.end(), cids.begin()); - cids.resize((ulong)(it - cids.begin())); - for (uint16_t i : cids) - { - if (std::find(new_states.begin(), new_states.end(), i) != new_states.end()) - { - if (std::find(states.begin(), states.end(), i) == states.end()) - std::thread{[=]() { dev->pressButton(i); }}.detach(); - } else - std::thread{[=]() { dev->releaseButton(i); }}.detach(); - } - states = new_states; - break; - } - case HIDPP20::IReprogControlsV4::Event::DivertedRawXYEvent: - { - auto raw_xy = HIDPP20::IReprogControlsV4::divertedRawXYEvent(event); - - for(uint16_t i : states) - std::thread{[=]() { dev->moveDiverted(i, raw_xy); }}.detach(); - break; - } - default: - break; - } -} - -void ReceiverHandler::handleEvent(const HIDPP::Report &event) -{ - switch(event.featureIndex()) - { - case HIDPP10::IReceiver::DeviceUnpaired: - { - log_printf(INFO, "%s (Device %d on %s) unpaired from receiver", dev->name.c_str(), dev->index, dev->path.c_str()); - std::thread {[=]() - { - finder->stopAndDeleteDevice(dev->path, dev->index); - finder->insertNewReceiverDevice(dev->path, dev->index); - }}.detach(); - break; - } - case HIDPP10::IReceiver::DevicePaired: - { - log_printf(DEBUG, "Receiver on %s: Device %d paired", dev->path.c_str(), event.deviceIndex()); - if(dev->waiting_for_receiver) - { - if(!dev->testConnection()) return; - dev->waiting_for_receiver = false; - dev->stop(); - } - //else: Likely an enumeration event, ignore. - break; - } - case HIDPP10::IReceiver::ConnectionStatus: - { - auto status = HIDPP10::IReceiver::connectionStatusEvent(event); - if(status == HIDPP10::IReceiver::LinkLoss) - { - log_printf(INFO, "Link lost to %s", dev->name.c_str()); - dev->disconnected = true; - } - else if (status == HIDPP10::IReceiver::ConnectionEstablished) - { - if(dev->waiting_for_receiver) - { - log_printf(DEBUG, "Receiver on %s: Connection established to device %d", dev->path.c_str(), event.deviceIndex()); - if(!dev->testConnection()) return; - dev->waiting_for_receiver = false; - std::thread { [=]() { dev->stop(); } }.detach(); - } - else - { - if(!dev->initialized) return; - dev->disconnected = false; - dev->configure(); - log_printf(INFO, "Connection established to %s", dev->name.c_str()); - } - } - break; - } - default: - break; - } -} - -void WirelessStatusHandler::handleEvent(const HIDPP::Report &event) -{ - switch(event.function()) - { - case HIDPP20::IWirelessDeviceStatus::StatusBroadcast: - { - auto status = HIDPP20::IWirelessDeviceStatus::statusBroadcastEvent(event); - if(status.ReconfNeeded) - dev->configure(); - break; - } - default: - { - log_printf(DEBUG, "Undocumented event %02x from WirelessDeviceStatus", event.function()); - break; - } - } -} - -void EventListener::removeEventHandlers () -{ - for (const auto &p: iterators) - dispatcher->unregisterEventHandler(p.second); - handlers.clear(); - iterators.clear(); -} - -EventListener::~EventListener() -{ - removeEventHandlers(); -} - -void EventListener::addEventHandler(std::unique_ptr &&handler) -{ - EventHandler *ptr = handler.get(); - for(uint8_t feature : handler->featureIndices()) - { - handlers.emplace(feature, std::move(handler)); - dispatcher->registerEventHandler(index, feature, [=](const HIDPP::Report &report) - { - ptr->handleEvent(report); - return true; - }); - } -} - -void SimpleListener::start() -{ - bool retry; - do - { - retry = false; - try { dispatcher->listen(); } - catch(std::system_error &e) - { - retry = true; - usleep(250000); - } - } while(retry && !stopped); - -} - -void SimpleListener::stop() -{ - this->stopped = true; - dispatcher->stop(); -} - -bool SimpleListener::event (EventHandler *handler, const HIDPP::Report &report) -{ - handler->handleEvent (report); - return true; -} - -void Device::stop() -{ - disconnected = true; - listener->stop(); -} - -void Device::pressButton(uint16_t cid) -{ - if(config->actions.find(cid) == config->actions.end()) - { - log_printf(DEBUG, "0x%x was pressed but no action was found.", cid); - return; - } - config->actions.find(cid)->second->press(); -} - -void Device::releaseButton(uint16_t cid) -{ - if(config->actions.find(cid) == config->actions.end()) - { - log_printf(DEBUG, "0x%x was released but no action was found.", cid); - return; - } - config->actions.find(cid)->second->release(); -} - -void Device::moveDiverted(uint16_t cid, HIDPP20::IReprogControlsV4::Move m) -{ - auto action = config->actions.find(cid); - if(action == config->actions.end()) - return; - switch(action->second->type) - { - case Action::Gestures: - ((GestureAction*)action->second)->move(m); - break; - default: - break; - } -} - -std::map Device::getFeatures() -{ - std::map _features; - HIDPP20::IFeatureSet ifs (hidpp_dev); - uint8_t feature_count = ifs.getCount(); - - for(uint8_t i = 0; i < feature_count; i++) - _features.insert( {i, ifs.getFeatureID(i) } ); - - return _features; + return _config->getSetting(_root_setting + '/' + path); } diff --git a/src/logid/Device.h b/src/logid/Device.h index 7fba0f7..2a0cd42 100644 --- a/src/logid/Device.h +++ b/src/logid/Device.h @@ -1,172 +1,106 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + #ifndef LOGID_DEVICE_H #define LOGID_DEVICE_H -#include "Actions.h" -#include "DeviceFinder.h" +#include "backend/hidpp/defs.h" +#include "backend/hidpp20/Device.h" +#include "backend/hidpp20/Feature.h" +#include "features/DeviceFeature.h" #include "Configuration.h" - -#include -#include -#include -#include -#include -#include -#include +#include "util/log.h" namespace logid { - class EventListener; - class DeviceConfig; + class Device; - class BlacklistedDevice : public std::exception + class DeviceConfig { public: - BlacklistedDevice() = default; - virtual const char* what() - { - return "Blacklisted device"; - } + DeviceConfig(const std::shared_ptr& config, Device* + device); + libconfig::Setting& getSetting(const std::string& path); + private: + Device* _device; + std::string _root_setting; + std::shared_ptr _config; }; + /* TODO: Implement HID++ 1.0 support + * Currently, the logid::Device class has a hardcoded requirement + * for an HID++ 2.0 device. + */ class Device { public: - Device(std::string p, const HIDPP::DeviceIndex i); - ~Device(); + Device(std::string path, backend::hidpp::DeviceIndex index); + Device(const std::shared_ptr& raw_device, + backend::hidpp::DeviceIndex index); - std::string name; + std::string name(); + uint16_t pid(); + + DeviceConfig& config(); + backend::hidpp20::Device& hidpp20(); + + void wakeup(); + void sleep(); - bool init(); - void configure(); void reset(); - void pressButton(uint16_t cid); - void releaseButton(uint16_t cid); - void moveDiverted(uint16_t cid, HIDPP20::IReprogControlsV4::Move move); - - void waitForReceiver(); - void start(); - void stop(); - bool testConnection(); - - std::map getFeatures(); - - std::map features; - - const std::string path; - const HIDPP::DeviceIndex index; - HIDPP::Dispatcher* dispatcher; - HIDPP20::Device* hidpp_dev; - - std::mutex configuring; - std::atomic_bool disconnected; - bool initialized = false; - bool waiting_for_receiver = false; - - protected: - DeviceConfig* config; - EventListener* listener; - - void divert_buttons(); - void printCIDs(); - void setSmartShift(HIDPP20::ISmartShift::SmartshiftStatus ops); - void setHiresScroll(uint8_t flags); - void setDPI(int dpi); - }; - - class EventHandler - { - public: - virtual const HIDPP20::FeatureInterface *feature() const = 0; - virtual const std::vector featureIndices() const - { - return {feature()->index()}; - }; - virtual void handleEvent (const HIDPP::Report &event) = 0; - }; - class ButtonHandler : public EventHandler - { - public: - ButtonHandler (Device *d) : dev (d), _irc (HIDPP20::IReprogControls::auto_version(d->hidpp_dev)) { } - const HIDPP20::FeatureInterface *feature () const - { - return &_irc; - } - void handleEvent (const HIDPP::Report &event); - protected: - Device* dev; - HIDPP20::IReprogControls _irc; - std::vector states; - std::vector new_states; - }; - class ReceiverHandler : public EventHandler - { - public: - ReceiverHandler (Device *d) : dev (d) { } - const HIDPP20::FeatureInterface *feature () const - { - return nullptr; // This sounds like a horrible idea - } - virtual const std::vector featureIndices() const - { - return HIDPP10::IReceiver::Events; - } - void handleEvent (const HIDPP::Report &event); - protected: - Device* dev; - }; - class WirelessStatusHandler : public EventHandler - { - public: - WirelessStatusHandler (Device *d) : dev (d), _iws (d->hidpp_dev) { } - const HIDPP20::FeatureInterface *feature () const - { - return &_iws; - } - void handleEvent (const HIDPP::Report &event); - protected: - Device* dev; - HIDPP20::IWirelessDeviceStatus _iws; - }; - - class EventListener - { - HIDPP::Dispatcher *dispatcher; - HIDPP::DeviceIndex index; - std::map> handlers; - std::map iterators; - public: - EventListener (HIDPP::Dispatcher *dispatcher, HIDPP::DeviceIndex index): dispatcher (dispatcher), index (index) {} - - virtual void removeEventHandlers (); - virtual ~EventListener(); - virtual void addEventHandler (std::unique_ptr &&handler); - - virtual void start () = 0; - virtual void stop () = 0; - - protected: - virtual bool event (EventHandler* handler, const HIDPP::Report &report) = 0; - }; - class SimpleListener : public EventListener - { - HIDPP::SimpleDispatcher *dispatcher; - - public: - SimpleListener (HIDPP::SimpleDispatcher* dispatcher, HIDPP::DeviceIndex index): - EventListener (dispatcher, index), - dispatcher (dispatcher) - { + template + std::shared_ptr getFeature(std::string name) { + auto it = _features.find(name); + if(it == _features.end()) + return nullptr; + try { + return std::dynamic_pointer_cast(it->second); + } catch(std::bad_cast& e) { + logPrintf(ERROR, "bad_cast while getting device feature %s: %s", + name.c_str(), e.what()); + return nullptr; + } } - bool stopped = false; - virtual void start(); - virtual void stop(); + private: + void _init(); - protected: - virtual bool event (EventHandler* handler, const HIDPP::Report &report); + /* Adds a feature without calling an error if unsupported */ + template + void _addFeature(std::string name) + { + try { + _features.emplace(name, std::make_shared(this)); + } catch (backend::hidpp20::UnsupportedFeature& e) { + } + } + + backend::hidpp20::Device _hidpp20; + std::string _path; + backend::hidpp::DeviceIndex _index; + std::map> + _features; + DeviceConfig _config; + + void _makeResetMechanism(); + std::unique_ptr> _reset_mechanism; }; - } #endif //LOGID_DEVICE_H diff --git a/src/logid/DeviceFinder.cpp b/src/logid/DeviceFinder.cpp deleted file mode 100644 index 53bd2a0..0000000 --- a/src/logid/DeviceFinder.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DeviceFinder.h" -#include "util.h" -#include "Device.h" - -#define NON_WIRELESS_DEV(index) (index) == HIDPP::DefaultDevice ? "default" : "corded" - -using namespace logid; - -void stopAndDeleteConnectedDevice (ConnectedDevice &connected_device) -{ - if(!connected_device.device->waiting_for_receiver) - log_printf(INFO, "%s (Device %d on %s) disconnected", connected_device.device->name.c_str(), - connected_device.device->index, connected_device.device->path.c_str()); - connected_device.device->stop(); - connected_device.associatedThread.join(); - delete(connected_device.device); -} - -DeviceFinder::~DeviceFinder() -{ - this->devices_mutex.lock(); - for (auto it = this->devices.begin(); it != this->devices.end(); it++) { - for (auto jt = it->second.begin(); jt != it->second.end(); jt++) { - stopAndDeleteConnectedDevice(jt->second); - } - } - this->devices_mutex.unlock(); -} - -///TODO: Unused return variable? -Device* DeviceFinder::insertNewDevice(const std::string &path, HIDPP::DeviceIndex index) -{ - auto device = new Device(path, index); - try - { - device->init(); - } - catch(BlacklistedDevice& e) { return nullptr; } - - this->devices_mutex.lock(); - log_printf(INFO, "%s detected: device %d on %s", device->name.c_str(), index, path.c_str()); - auto path_bucket = this->devices.emplace(path, std::map()).first; - path_bucket->second.emplace(index, ConnectedDevice{ - device, - std::thread([device]() { - device->start(); - }) - }); - this->devices_mutex.unlock(); - - return device; -} - -Device* DeviceFinder::insertNewReceiverDevice(const std::string &path, HIDPP::DeviceIndex index) -{ - auto *device = new Device(path, index); - - this->devices_mutex.lock(); - auto path_bucket = this->devices.emplace(path, std::map()).first; - path_bucket->second.emplace(index, ConnectedDevice{ - device, - std::thread([device]() { - device->waitForReceiver(); - }) - }); - this->devices_mutex.unlock(); - - return device; -} - -void DeviceFinder::stopAndDeleteAllDevicesIn (const std::string &path) -{ - this->devices_mutex.lock(); - auto path_bucket = this->devices.find(path); - if (path_bucket != this->devices.end()) - { - for (auto& index_bucket : path_bucket->second) { - stopAndDeleteConnectedDevice(index_bucket.second); - } - this->devices.erase(path_bucket); - } - this->devices_mutex.unlock(); -} - -void DeviceFinder::stopAndDeleteDevice (const std::string &path, HIDPP::DeviceIndex index) -{ - this->devices_mutex.lock(); - auto path_bucket = this->devices.find(path); - if (path_bucket != this->devices.end()) - { - auto index_bucket = path_bucket->second.find(index); - if (index_bucket != path_bucket->second.end()) - { - stopAndDeleteConnectedDevice(index_bucket->second); - path_bucket->second.erase(index_bucket); - } - } - this->devices_mutex.unlock(); - - log_printf(WARN, "Attempted to disconnect not previously connected device %d on %s", index, path.c_str()); -} - -void DeviceFinder::addDevice(const char *path) -{ - using namespace std::chrono_literals; - - std::string string_path(path); - // Asynchronously scan device - std::thread{[=]() - { - //Check if device is an HID++ device and handle it accordingly - try - { - HIDPP::SimpleDispatcher dispatcher(string_path.c_str()); - for(HIDPP::DeviceIndex index: { HIDPP::DefaultDevice, HIDPP::CordedDevice }) - { - bool device_not_connected = true; - bool device_unknown = false; - int remaining_tries = MAX_CONNECTION_TRIES; - do { - try - { - HIDPP::Device d(&dispatcher, index); - auto version = d.protocolVersion(); - uint major, minor; - std::tie(major, minor) = version; - if(index == HIDPP::DefaultDevice && version == std::make_tuple(1, 0)) - { - HIDPP10::Device receiver(&dispatcher, index); - HIDPP10::IReceiver irecv(&receiver); - log_printf(INFO, "Found %s on %s", receiver.name().c_str(), string_path.c_str()); - for(HIDPP::DeviceIndex recv_index : { HIDPP::WirelessDevice1, HIDPP::WirelessDevice2, - HIDPP::WirelessDevice3, HIDPP::WirelessDevice4, - HIDPP::WirelessDevice5, HIDPP::WirelessDevice6 }) - this->insertNewReceiverDevice(string_path, recv_index); - irecv.getPairedDevices(); - return; - } - if(major > 1) // HID++ 2.0 devices only - { - this->insertNewDevice(string_path, index); - } - device_not_connected = false; - } - catch(HIDPP10::Error &e) - { - if (e.errorCode() == HIDPP10::Error::ResourceError) - { - if(remaining_tries == 1) - { - log_printf(DEBUG, "While querying %s (possibly asleep), %s device: %s", string_path.c_str(), NON_WIRELESS_DEV(index), e.what()); - remaining_tries += MAX_CONNECTION_TRIES; // asleep devices may raise a resource error, so do not count this try - } - } - else if(e.errorCode() != HIDPP10::Error::UnknownDevice) - { - if(remaining_tries == 1) - log_printf(ERROR, "While querying %s, %s device: %s", string_path.c_str(), NON_WIRELESS_DEV(index), e.what()); - } - else device_unknown = true; - } - catch(HIDPP20::Error &e) - { - if(e.errorCode() != HIDPP20::Error::UnknownDevice) - { - if(remaining_tries == 1) - log_printf(ERROR, "Error while querying %s, device %d: %s", string_path.c_str(), NON_WIRELESS_DEV(index), e.what()); - } - else device_unknown = true; - } - catch(HIDPP::Dispatcher::TimeoutError &e) - { - if(remaining_tries == 1) - { - log_printf(DEBUG, "Time out on %s device: %s (possibly asleep)", NON_WIRELESS_DEV(index), string_path.c_str()); - remaining_tries += MAX_CONNECTION_TRIES; // asleep devices may raise a timeout error, so do not count this try - } - } - catch(std::runtime_error &e) - { - if(remaining_tries == 1) - log_printf(ERROR, "Runtime error on %s device on %s: %s", NON_WIRELESS_DEV(index), string_path.c_str(), e.what()); - } - - remaining_tries--; - std::this_thread::sleep_for(TIME_BETWEEN_CONNECTION_TRIES); - - } while (device_not_connected && !device_unknown && remaining_tries > 0); - } - } - catch(HIDPP::Dispatcher::NoHIDPPReportException &e) { } - catch(std::system_error &e) { log_printf(WARN, "Failed to open %s: %s", string_path.c_str(), e.what()); } - }}.detach(); -} - -void DeviceFinder::removeDevice(const char* path) -{ - this->stopAndDeleteAllDevicesIn(std::string(path)); -} diff --git a/src/logid/DeviceFinder.h b/src/logid/DeviceFinder.h deleted file mode 100644 index 687b33d..0000000 --- a/src/logid/DeviceFinder.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef LOGID_DEVICEFINDER_H -#define LOGID_DEVICEFINDER_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Device.h" - -#define MAX_CONNECTION_TRIES 10 -#define TIME_BETWEEN_CONNECTION_TRIES 500ms - -namespace logid -{ - - class Device; - - struct ConnectedDevice { - Device *device; - std::thread associatedThread; - }; - - class DeviceFinder : public HID::DeviceMonitor - { - public: - ~DeviceFinder(); - - Device* insertNewDevice (const std::string &path, HIDPP::DeviceIndex index); - Device* insertNewReceiverDevice (const std::string &path, HIDPP::DeviceIndex index); - void stopAndDeleteAllDevicesIn (const std::string &path); - void stopAndDeleteDevice (const std::string &path, HIDPP::DeviceIndex index); - protected: - void addDevice(const char* path); - void removeDevice(const char* path); - private: - std::mutex devices_mutex; - std::map> devices; - }; - - extern DeviceFinder* finder; -} - -#endif //LOGID_DEVICEFINDER_H \ No newline at end of file diff --git a/src/logid/DeviceManager.cpp b/src/logid/DeviceManager.cpp new file mode 100644 index 0000000..e8016d4 --- /dev/null +++ b/src/logid/DeviceManager.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include "DeviceManager.h" +#include "Receiver.h" +#include "util/log.h" +#include "backend/hidpp10/Error.h" +#include "backend/Error.h" + +using namespace logid; +using namespace logid::backend; + +void DeviceManager::addDevice(std::string path) +{ + bool defaultExists = true; + bool isReceiver = false; + + // Check if device is ignored before continuing + { + raw::RawDevice raw_dev(path); + if(global_config->isIgnored(raw_dev.productId())) { + logPrintf(DEBUG, "%s: Device 0x%04x ignored.", + path.c_str(), raw_dev.productId()); + return; + } + } + + try { + hidpp::Device device(path, hidpp::DefaultDevice); + isReceiver = device.version() == std::make_tuple(1, 0); + } catch(hidpp10::Error &e) { + if(e.code() != hidpp10::Error::UnknownDevice) + throw; + } catch(hidpp::Device::InvalidDevice &e) { // Ignore + defaultExists = false; + } catch(std::system_error &e) { + logPrintf(WARN, "I/O error on %s: %s, skipping device.", + path.c_str(), e.what()); + return; + } catch (TimeoutError &e) { + logPrintf(WARN, "Device %s timed out.", path.c_str()); + defaultExists = false; + } + + if(isReceiver) { + logPrintf(INFO, "Detected receiver at %s", path.c_str()); + auto receiver = std::make_shared(path); + receiver->run(); + _receivers.emplace(path, receiver); + } else { + /* TODO: Can non-receivers only contain 1 device? + * If the device exists, it is guaranteed to be an HID++ 2.0 device */ + if(defaultExists) { + auto device = std::make_shared(path, hidpp::DefaultDevice); + _devices.emplace(path, device); + } else { + try { + auto device = std::make_shared(path, + hidpp::CordedDevice); + _devices.emplace(path, device); + } catch(hidpp10::Error &e) { + if(e.code() != hidpp10::Error::UnknownDevice) + throw; + else + logPrintf(WARN, + "HID++ 1.0 error while trying to initialize %s:" + "%s", path.c_str(), e.what()); + } catch(hidpp::Device::InvalidDevice &e) { // Ignore + } catch(std::system_error &e) { + // This error should have been thrown previously + logPrintf(WARN, "I/O error on %s: %s", path.c_str(), + e.what()); + } + } + } +} + +void DeviceManager::removeDevice(std::string path) +{ + auto receiver = _receivers.find(path); + + if(receiver != _receivers.end()) { + _receivers.erase(receiver); + logPrintf(INFO, "Receiver on %s disconnected", path.c_str()); + } else { + auto device = _devices.find(path); + if(device != _devices.find(path)) { + _devices.erase(device); + logPrintf(INFO, "Device on %s disconnected", path.c_str()); + } + } +} diff --git a/src/logid/DeviceManager.h b/src/logid/DeviceManager.h new file mode 100644 index 0000000..6305963 --- /dev/null +++ b/src/logid/DeviceManager.h @@ -0,0 +1,50 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_DEVICEMANAGER_H +#define LOGID_DEVICEMANAGER_H + +#include +#include +#include + +#include "backend/raw/DeviceMonitor.h" +#include "backend/hidpp/Device.h" +#include "Device.h" +#include "Receiver.h" + +namespace logid +{ + + class DeviceManager : public backend::raw::DeviceMonitor + { + public: + DeviceManager() = default; + protected: + void addDevice(std::string path) override; + void removeDevice(std::string path) override; + private: + + std::map> _devices; + std::map> _receivers; + }; + + extern std::unique_ptr device_manager; +} + +#endif //LOGID_DEVICEMANAGER_H \ No newline at end of file diff --git a/src/logid/EvdevDevice.cpp b/src/logid/EvdevDevice.cpp deleted file mode 100644 index 5ff21c4..0000000 --- a/src/logid/EvdevDevice.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include - -#include "EvdevDevice.h" - -using namespace logid; - -EvdevDevice::EvdevDevice(const char* name) -{ - device = libevdev_new(); - libevdev_set_name(device, name); - - libevdev_enable_event_type(device, EV_KEY); - for(int i = 0; i < KEY_CNT; i++) - libevdev_enable_event_code(device, EV_KEY, i, nullptr); - libevdev_enable_event_type(device, EV_REL); - for(int i = 0; i < REL_CNT; i++) - libevdev_enable_event_code(device, EV_REL, i, nullptr); - - int err = libevdev_uinput_create_from_device(device, LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device); - - if(err != 0) - throw std::system_error(-err, std::generic_category()); -} - -void EvdevDevice::moveAxis(unsigned int axis, int movement) -{ - libevdev_uinput_write_event(ui_device, EV_REL, axis, movement); - libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0); -} - -void EvdevDevice::sendEvent(unsigned int type, unsigned int code, int value) -{ - libevdev_uinput_write_event(ui_device, type, code, value); - libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0); -} - -EvdevDevice::~EvdevDevice() -{ - libevdev_uinput_destroy(ui_device); - libevdev_free(device); -} diff --git a/src/logid/EvdevDevice.h b/src/logid/EvdevDevice.h deleted file mode 100644 index c37c2aa..0000000 --- a/src/logid/EvdevDevice.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef LOGID_EVDEVDEVICE_H -#define LOGID_EVDEVDEVICE_H - -#include -#include - -namespace logid -{ - class EvdevDevice - { - public: - EvdevDevice(const char *name); - - ~EvdevDevice(); - - void moveAxis(unsigned int axis, int movement); - - void sendEvent(unsigned int type, unsigned int code, int value); - - libevdev *device; - libevdev_uinput *ui_device; - }; - - extern EvdevDevice* global_evdev; -} - -#endif //LOGID_EVDEVDEVICE_H \ No newline at end of file diff --git a/src/logid/InputDevice.cpp b/src/logid/InputDevice.cpp new file mode 100644 index 0000000..aecf548 --- /dev/null +++ b/src/logid/InputDevice.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include + +#include "InputDevice.h" + +extern "C" +{ +#include +#include +} + +using namespace logid; + +InputDevice::InvalidEventCode::InvalidEventCode(const std::string& name) : + _what ("Invalid event code " + name) +{ +} + +const char* InputDevice::InvalidEventCode::what() const noexcept +{ + return _what.c_str(); +} + +InputDevice::InputDevice(const char* name) +{ + device = libevdev_new(); + libevdev_set_name(device, name); + + ///TODO: Is it really a good idea to enable all events? + libevdev_enable_event_type(device, EV_KEY); + for(unsigned int i = 0; i < KEY_CNT; i++) + libevdev_enable_event_code(device, EV_KEY, i, nullptr); + libevdev_enable_event_type(device, EV_REL); + for(unsigned int i = 0; i < REL_CNT; i++) + libevdev_enable_event_code(device, EV_REL, i, nullptr); + + int err = libevdev_uinput_create_from_device(device, + LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_device); + + if(err != 0) + throw std::system_error(-err, std::generic_category()); +} + +InputDevice::~InputDevice() +{ + libevdev_uinput_destroy(ui_device); + libevdev_free(device); +} + +void InputDevice::moveAxis(uint axis, int movement) +{ + _sendEvent(EV_REL, axis, movement); +} + +void InputDevice::pressKey(uint code) +{ + _sendEvent(EV_KEY, code, 1); +} + +void InputDevice::releaseKey(uint code) +{ + _sendEvent(EV_KEY, code, 0); +} + +uint InputDevice::toKeyCode(const std::string& name) +{ + return _toEventCode(EV_KEY, name); +} + +uint InputDevice::toAxisCode(const std::string& name) +{ + return _toEventCode(EV_REL, name); +} + +uint InputDevice::_toEventCode(uint type, const std::string& name) +{ + int code = libevdev_event_code_from_name(type, name.c_str()); + + if(code == -1) + throw InvalidEventCode(name); + + return code; +} + +void InputDevice::_sendEvent(uint type, uint code, int value) +{ + libevdev_uinput_write_event(ui_device, type, code, value); + libevdev_uinput_write_event(ui_device, EV_SYN, SYN_REPORT, 0); +} \ No newline at end of file diff --git a/src/logid/InputDevice.h b/src/logid/InputDevice.h new file mode 100644 index 0000000..5b2cd22 --- /dev/null +++ b/src/logid/InputDevice.h @@ -0,0 +1,64 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_INPUTDEVICE_H +#define LOGID_INPUTDEVICE_H + +#include + +extern "C" +{ +#include +#include +} + +namespace logid +{ + class InputDevice + { + public: + class InvalidEventCode : public std::exception + { + public: + explicit InvalidEventCode(const std::string& name); + const char* what() const noexcept override; + private: + const std::string _what; + }; + explicit InputDevice(const char *name); + ~InputDevice(); + + void moveAxis(uint axis, int movement); + void pressKey(uint code); + void releaseKey(uint code); + + static uint toKeyCode(const std::string& name); + static uint toAxisCode(const std::string& name); + private: + void _sendEvent(uint type, uint code, int value); + + static uint _toEventCode(uint type, const std::string& name); + + libevdev* device; + libevdev_uinput* ui_device{}; + }; + + extern std::unique_ptr virtual_input; +} + +#endif //LOGID_INPUTDEVICE_H \ No newline at end of file diff --git a/src/logid/Receiver.cpp b/src/logid/Receiver.cpp new file mode 100644 index 0000000..15595e5 --- /dev/null +++ b/src/logid/Receiver.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "Receiver.h" +#include "util/log.h" +#include "backend/hidpp10/Error.h" +#include "backend/hidpp20/Error.h" +#include "backend/Error.h" + +using namespace logid; +using namespace logid::backend; + +Receiver::Receiver(const std::string& path) : + dj::ReceiverMonitor(path), _path (path) +{ +} + +void Receiver::addDevice(hidpp::DeviceConnectionEvent event) +{ + std::unique_lock lock(_devices_change); + try { + // Check if device is ignored before continuing + if(global_config->isIgnored(event.pid)) { + logPrintf(DEBUG, "%s:%d: Device 0x%04x ignored.", + _path.c_str(), event.index, event.pid); + return; + } + + auto dev = _devices.find(event.index); + if(dev != _devices.end()) { + if(event.linkEstablished) + dev->second->wakeup(); + else + dev->second->sleep(); + return; + } + + if(!event.linkEstablished) + return; + + hidpp::Device hidpp_device(receiver(), event); + + auto version = hidpp_device.version(); + + if(std::get<0>(version) < 2) { + logPrintf(INFO, "Unsupported HID++ 1.0 device on %s:%d connected.", + _path.c_str(), event.index); + return; + } + + std::shared_ptr device = std::make_shared( + receiver()->rawDevice(), event.index); + + _devices.emplace(event.index, device); + + } catch(hidpp10::Error &e) { + logPrintf(ERROR, + "Caught HID++ 1.0 error while trying to initialize " + "%s:%d: %s", _path.c_str(), event.index, e.what()); + } catch(hidpp20::Error &e) { + logPrintf(ERROR, "Caught HID++ 2.0 error while trying to initialize " + "%s:%d: %s", _path.c_str(), event.index, e.what()); + } catch(TimeoutError &e) { + if(!event.fromTimeoutCheck) + logPrintf(DEBUG, "%s:%d timed out, waiting for input from device to" + " initialize.", _path.c_str(), event.index); + waitForDevice(event.index); + } +} + +void Receiver::removeDevice(hidpp::DeviceIndex index) +{ + std::unique_lock lock(_devices_change); + _devices.erase(index); +} \ No newline at end of file diff --git a/src/logid/Receiver.h b/src/logid/Receiver.h new file mode 100644 index 0000000..844a42d --- /dev/null +++ b/src/logid/Receiver.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_RECEIVER_H +#define LOGID_RECEIVER_H + +#include +#include "backend/dj/ReceiverMonitor.h" +#include "Device.h" + +namespace logid +{ + class Receiver : public backend::dj::ReceiverMonitor + { + public: + Receiver(const std::string& path); + + protected: + void addDevice(backend::hidpp::DeviceConnectionEvent event) override; + void removeDevice(backend::hidpp::DeviceIndex index) override; + private: + std::mutex _devices_change; + std::map> _devices; + std::string _path; + }; +} + +#endif //LOGID_RECEIVER_H \ No newline at end of file diff --git a/src/logid/actions/Action.cpp b/src/logid/actions/Action.cpp new file mode 100644 index 0000000..307f5df --- /dev/null +++ b/src/logid/actions/Action.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "Action.h" +#include "../util/log.h" +#include "KeypressAction.h" +#include "ToggleSmartShift.h" +#include "ToggleHiresScroll.h" +#include "GestureAction.h" +#include "NullAction.h" +#include "CycleDPI.h" +#include "ChangeDPI.h" + +using namespace logid; +using namespace logid::actions; + +std::shared_ptr Action::makeAction(Device *device, libconfig::Setting + &setting) +{ + if(!setting.isGroup()) { + logPrintf(WARN, "Line %d: Action is not a group, ignoring.", + setting.getSourceLine()); + throw InvalidAction(); + } + + try { + auto& action_type = setting.lookup("type"); + + if(action_type.getType() != libconfig::Setting::TypeString) { + logPrintf(WARN, "Line %d: Action type must be a string", + action_type.getSourceLine()); + throw InvalidAction(); + } + + std::string type = action_type; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); + + if(type == "keypress") + return std::make_shared(device, setting); + else if(type == "togglesmartshift") + return std::make_shared(device); + else if(type == "togglehiresscroll") + return std::make_shared(device); + else if(type == "gestures") + return std::make_shared(device, setting); + else if(type == "cycledpi") + return std::make_shared(device, setting); + else if(type == "changedpi") + return std::make_shared(device, setting); + else if(type == "none") + return std::make_shared(device); + else + throw InvalidAction(type); + + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: Action type is missing, ignoring.", + setting.getSourceLine()); + throw InvalidAction(); + } +} \ No newline at end of file diff --git a/src/logid/actions/Action.h b/src/logid/actions/Action.h new file mode 100644 index 0000000..846abd7 --- /dev/null +++ b/src/logid/actions/Action.h @@ -0,0 +1,83 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_H +#define LOGID_ACTION_H + +#include +#include +#include + +namespace logid { + class Device; +namespace actions { + class InvalidAction : public std::exception + { + public: + InvalidAction() + { + } + explicit InvalidAction(std::string& action) : _action (action) + { + } + const char* what() const noexcept override + { + return _action.c_str(); + } + private: + std::string _action; + }; + + class Action + { + public: + static std::shared_ptr makeAction(Device* device, + libconfig::Setting& setting); + + virtual void press() = 0; + virtual void release() = 0; + virtual void move(int16_t x, int16_t y) + { + // Suppress unused warning + (void)x; (void)y; + } + + virtual bool pressed() + { + return _pressed; + } + + virtual uint8_t reprogFlags() const = 0; + + class Config + { + protected: + explicit Config(Device* device) : _device (device) + { + } + Device* _device; + }; + protected: + explicit Action(Device* device) : _device (device), _pressed (false) + { + } + Device* _device; + std::atomic _pressed; + }; +}} + +#endif //LOGID_ACTION_H diff --git a/src/logid/actions/ChangeDPI.cpp b/src/logid/actions/ChangeDPI.cpp new file mode 100644 index 0000000..faab6b9 --- /dev/null +++ b/src/logid/actions/ChangeDPI.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "ChangeDPI.h" +#include "../Device.h" +#include "../util/task.h" +#include "../util/log.h" +#include "../backend/hidpp20/Error.h" +#include "../backend/hidpp20/features/ReprogControls.h" + +using namespace logid::actions; + +ChangeDPI::ChangeDPI(Device *device, libconfig::Setting &setting) : + Action(device), _config(device, setting) +{ + _dpi = _device->getFeature("dpi"); + if(!_dpi) + logPrintf(WARN, "%s:%d: DPI feature not found, cannot use " + "ChangeDPI action.", + _device->hidpp20().devicePath().c_str(), + _device->hidpp20().deviceIndex()); +} + +void ChangeDPI::press() +{ + _pressed = true; + if(_dpi) { + task::spawn([this]{ + try { + uint16_t last_dpi = _dpi->getDPI(_config.sensor()); + _dpi->setDPI(last_dpi + _config.interval(), _config.sensor()); + } catch (backend::hidpp20::Error& e) { + if(e.code() == backend::hidpp20::Error::InvalidArgument) + logPrintf(WARN, "%s:%d: Could not get/set DPI for sensor " + "%d", + _device->hidpp20().devicePath().c_str(), + _device->hidpp20().deviceIndex(), + _config.sensor()); + else + throw e; + } + }); + } +} + +void ChangeDPI::release() +{ + _pressed = false; +} + +uint8_t ChangeDPI::reprogFlags() const +{ + return backend::hidpp20::ReprogControls::TemporaryDiverted; +} + +ChangeDPI::Config::Config(Device *device, libconfig::Setting &config) : + Action::Config(device), _interval (0), _sensor (0) +{ + if(!config.isGroup()) { + logPrintf(WARN, "Line %d: action must be an object, skipping.", + config.getSourceLine()); + return; + } + + try { + auto& inc = config.lookup("inc"); + if(inc.getType() != libconfig::Setting::TypeInt) + logPrintf(WARN, "Line %d: inc must be an integer", + inc.getSourceLine()); + _interval = (int)inc; + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: inc is a required field, skipping.", + config.getSourceLine()); + } + + try { + auto& sensor = config.lookup("sensor"); + if(sensor.getType() != libconfig::Setting::TypeInt) + logPrintf(WARN, "Line %d: sensor must be an integer", + sensor.getSourceLine()); + _sensor = (int)sensor; + } catch(libconfig::SettingNotFoundException& e) { + // Ignore + } +} + +uint16_t ChangeDPI::Config::interval() const +{ + return _interval; +} + +uint8_t ChangeDPI::Config::sensor() const +{ + return _sensor; +} \ No newline at end of file diff --git a/src/logid/actions/ChangeDPI.h b/src/logid/actions/ChangeDPI.h new file mode 100644 index 0000000..4d9e04f --- /dev/null +++ b/src/logid/actions/ChangeDPI.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_CHANGEDPI_H +#define LOGID_ACTION_CHANGEDPI_H + +#include +#include "Action.h" +#include "../features/DPI.h" + +namespace logid { + namespace actions { + class ChangeDPI : public Action + { + public: + explicit ChangeDPI(Device* device, libconfig::Setting& setting); + + virtual void press(); + virtual void release(); + + virtual uint8_t reprogFlags() const; + + class Config : public Action::Config + { + public: + Config(Device* device, libconfig::Setting& setting); + uint16_t interval() const; + uint8_t sensor() const; + private: + uint16_t _interval; + uint8_t _sensor; + }; + + protected: + Config _config; + std::shared_ptr _dpi; + }; + }} + +#endif //LOGID_ACTION_CHANGEDPI_H \ No newline at end of file diff --git a/src/logid/actions/CycleDPI.cpp b/src/logid/actions/CycleDPI.cpp new file mode 100644 index 0000000..19b729c --- /dev/null +++ b/src/logid/actions/CycleDPI.cpp @@ -0,0 +1,133 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "CycleDPI.h" +#include "../Device.h" +#include "../util/task.h" +#include "../util/log.h" +#include "../backend/hidpp20/Error.h" +#include "../backend/hidpp20/features/ReprogControls.h" + +using namespace logid::actions; +using namespace libconfig; + +CycleDPI::CycleDPI(Device* device, libconfig::Setting& setting) : + Action (device), _config (device, setting) +{ + _dpi = _device->getFeature("dpi"); + if(!_dpi) + logPrintf(WARN, "%s:%d: DPI feature not found, cannot use " + "CycleDPI action.", + _device->hidpp20().devicePath().c_str(), + _device->hidpp20().deviceIndex()); +} + +void CycleDPI::press() +{ + _pressed = true; + if(_dpi && !_config.empty()) { + task::spawn([this](){ + uint16_t dpi = _config.nextDPI(); + try { + _dpi->setDPI(dpi, _config.sensor()); + } catch (backend::hidpp20::Error& e) { + if(e.code() == backend::hidpp20::Error::InvalidArgument) + logPrintf(WARN, "%s:%d: Could not set DPI to %d for " + "sensor %d", _device->hidpp20().devicePath().c_str(), + _device->hidpp20().deviceIndex(), dpi, + _config.sensor()); + else + throw e; + } + }); + } +} + +void CycleDPI::release() +{ + _pressed = false; +} + +uint8_t CycleDPI::reprogFlags() const +{ + return backend::hidpp20::ReprogControls::TemporaryDiverted; +} + +CycleDPI::Config::Config(Device *device, libconfig::Setting &config) : + Action::Config(device), _current_index (0), _sensor (0) +{ + if(!config.isGroup()) { + logPrintf(WARN, "Line %d: action must be an object, skipping.", + config.getSourceLine()); + return; + } + + try { + auto& sensor = config.lookup("sensor"); + if(sensor.getType() != Setting::TypeInt) + logPrintf(WARN, "Line %d: sensor must be an integer", + sensor.getSourceLine()); + _sensor = (int)sensor; + } catch(libconfig::SettingNotFoundException& e) { + // Ignore + } + + try { + auto& dpis = config.lookup("dpis"); + if(!dpis.isList() && !dpis.isArray()) { + logPrintf(WARN, "Line %d: dpis must be a list or array, skipping.", + dpis.getSourceLine()); + return; + } + + int dpi_count = dpis.getLength(); + for(int i = 0; i < dpi_count; i++) { + if(dpis[i].getType() != Setting::TypeInt) { + logPrintf(WARN, "Line %d: dpis must be integers, skipping.", + dpis[i].getSourceLine()); + if(dpis.isList()) + continue; + else + break; + } + + _dpis.push_back((int)(dpis[i])); + } + + } catch (libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: dpis is a required field, skipping.", + config.getSourceLine()); + } +} + +uint16_t CycleDPI::Config::nextDPI() +{ + uint16_t dpi = _dpis[_current_index++]; + if(_current_index >= _dpis.size()) + _current_index = 0; + return dpi; +} + +bool CycleDPI::Config::empty() const +{ + return _dpis.empty(); +} + +uint8_t CycleDPI::Config::sensor() const +{ + return _sensor; +} \ No newline at end of file diff --git a/src/logid/actions/CycleDPI.h b/src/logid/actions/CycleDPI.h new file mode 100644 index 0000000..b6a5017 --- /dev/null +++ b/src/logid/actions/CycleDPI.h @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_CYCLEDPI_H +#define LOGID_ACTION_CYCLEDPI_H + +#include +#include "Action.h" +#include "../features/DPI.h" + +namespace logid { +namespace actions { + class CycleDPI : public Action + { + public: + explicit CycleDPI(Device* device, libconfig::Setting& setting); + + virtual void press(); + virtual void release(); + + virtual uint8_t reprogFlags() const; + + class Config : public Action::Config + { + public: + Config(Device* device, libconfig::Setting& setting); + uint16_t nextDPI(); + bool empty() const; + uint8_t sensor() const; + private: + std::size_t _current_index; + std::vector _dpis; + uint8_t _sensor; + }; + + protected: + Config _config; + std::shared_ptr _dpi; + }; +}} + +#endif //LOGID_ACTION_CYCLEDPI_H \ No newline at end of file diff --git a/src/logid/actions/GestureAction.cpp b/src/logid/actions/GestureAction.cpp new file mode 100644 index 0000000..a9b80f4 --- /dev/null +++ b/src/logid/actions/GestureAction.cpp @@ -0,0 +1,269 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "GestureAction.h" +#include "../Device.h" +#include "../backend/hidpp20/features/ReprogControls.h" + +using namespace logid::actions; +using namespace logid; +using namespace logid::backend; + +GestureAction::Direction GestureAction::toDirection(std::string direction) +{ + std::transform(direction.begin(), direction.end(), direction.begin(), + ::tolower); + if(direction == "up") + return Up; + else if(direction == "down") + return Down; + else if(direction == "left") + return Left; + else if(direction == "right") + return Right; + else if(direction == "none") + return None; + else + throw std::invalid_argument("direction"); +} + +GestureAction::Direction GestureAction::toDirection(int16_t x, int16_t y) +{ + if(x >= 0 && y >= 0) + return x >= y ? Right : Down; + else if(x < 0 && y >= 0) + return -x <= y ? Down : Left; + else if(x <= 0 && y < 0) + return x <= y ? Left : Up; + else + return x <= -y ? Up : Right; +} + +GestureAction::GestureAction(Device* dev, libconfig::Setting& config) : + Action (dev), _config (dev, config) +{ +} + +void GestureAction::press() +{ + _pressed = true; + _x = 0, _y = 0; + for(auto& gesture : _config.gestures()) + gesture.second->press(); +} + +void GestureAction::release() +{ + _pressed = false; + bool threshold_met = false; + + auto d = toDirection(_x, _y); + auto primary_gesture = _config.gestures().find(d); + if(primary_gesture != _config.gestures().end()) { + threshold_met = primary_gesture->second->metThreshold(); + primary_gesture->second->release(true); + } + + for(auto& gesture : _config.gestures()) { + if(gesture.first == d) + continue; + if(!threshold_met) { + if(gesture.second->metThreshold()) { + // If the primary gesture did not meet its threshold, use the + // secondary one. + threshold_met = true; + gesture.second->release(true); + break; + } + } else { + gesture.second->release(false); + } + } + + if(!threshold_met) { + if(_config.noneAction()) { + _config.noneAction()->press(); + _config.noneAction()->release(); + } + } +} + +void GestureAction::move(int16_t x, int16_t y) +{ + auto new_x = _x + x, new_y = _y + y; + + if(abs(x) > 0) { + if(_x < 0 && new_x >= 0) { // Left -> Origin/Right + auto left = _config.gestures().find(Left); + if(left != _config.gestures().end()) + left->second->move(_x); + if(new_x) { // Ignore to origin + auto right = _config.gestures().find(Right); + if(right != _config.gestures().end()) + right->second->move(new_x); + } + } else if(_x > 0 && new_x <= 0) { // Right -> Origin/Left + auto right = _config.gestures().find(Right); + if(right != _config.gestures().end()) + right->second->move(-_x); + if(new_x) { // Ignore to origin + auto left = _config.gestures().find(Left); + if(left != _config.gestures().end()) + left->second->move(-new_x); + } + } else if(new_x < 0) { // Origin/Left to Left + auto left = _config.gestures().find(Left); + if(left != _config.gestures().end()) + left->second->move(-x); + } else if(new_x > 0) { // Origin/Right to Right + auto right = _config.gestures().find(Right); + if(right != _config.gestures().end()) + right->second->move(x); + } + } + + if(abs(y) > 0) { + if(_y > 0 && new_y <= 0) { // Up -> Origin/Down + auto up = _config.gestures().find(Up); + if(up != _config.gestures().end()) + up->second->move(_y); + if(new_y) { // Ignore to origin + auto down = _config.gestures().find(Down); + if(down != _config.gestures().end()) + down->second->move(new_y); + } + } else if(_y < 0 && new_y >= 0) { // Down -> Origin/Up + auto down = _config.gestures().find(Down); + if(down != _config.gestures().end()) + down->second->move(-_y); + if(new_y) { // Ignore to origin + auto up = _config.gestures().find(Up); + if(up != _config.gestures().end()) + up->second->move(-new_y); + } + } else if(new_y < 0) { // Origin/Up to Up + auto up = _config.gestures().find(Up); + if(up != _config.gestures().end()) + up->second->move(-y); + } else if(new_y > 0) {// Origin/Down to Down + auto down = _config.gestures().find(Down); + if(down != _config.gestures().end()) + down->second->move(y); + } + } + + _x = new_x; _y = new_y; +} + +uint8_t GestureAction::reprogFlags() const +{ + return (hidpp20::ReprogControls::TemporaryDiverted | + hidpp20::ReprogControls::RawXYDiverted); +} + +GestureAction::Config::Config(Device* device, libconfig::Setting &root) : + Action::Config(device) +{ + try { + auto& gestures = root.lookup("gestures"); + + if(!gestures.isList()) { + logPrintf(WARN, "Line %d: gestures must be a list, ignoring.", + gestures.getSourceLine()); + return; + } + + int gesture_count = gestures.getLength(); + + for(int i = 0; i < gesture_count; i++) { + if(!gestures[i].isGroup()) { + logPrintf(WARN, "Line %d: gesture must be a group, skipping.", + gestures[i].getSourceLine()); + continue; + } + + Direction d; + try { + auto& direction = gestures[i].lookup("direction"); + if(direction.getType() != libconfig::Setting::TypeString) { + logPrintf(WARN, "Line %d: direction must be a string, " + "skipping.", direction.getSourceLine()); + continue; + } + + try { + d = toDirection(direction); + } catch(std::invalid_argument& e) { + logPrintf(WARN, "Line %d: Invalid direction %s", + direction.getSourceLine(), (const char*)direction); + continue; + } + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: direction is a required field, " + "skipping.", gestures[i].getSourceLine()); + continue; + } + + if(_gestures.find(d) != _gestures.end() || (d == None && _none_action)) { + logPrintf(WARN, "Line %d: Gesture is already defined for " + "this direction, duplicate ignored.", + gestures[i].getSourceLine()); + continue; + } + + if(d == None) { + try { + _none_action = Action::makeAction(_device, + gestures[i].lookup("action")); + } catch (InvalidAction& e) { + logPrintf(WARN, "Line %d: %s is not a valid action, " + "skipping.", gestures[i].lookup("action") + .getSourceLine(), e.what()); + } catch (libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: action is a required field, " + "skipping.", gestures[i].getSourceLine(), + e.what()); + } + continue; + } + + try { + _gestures.emplace(d, Gesture::makeGesture(_device, + gestures[i])); + } catch(InvalidGesture& e) { + logPrintf(WARN, "Line %d: Invalid gesture: %s", + gestures[i].getSourceLine(), e.what()); + } + } + + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: gestures is a required field, ignoring.", + root.getSourceLine()); + } +} + +std::map>& + GestureAction::Config::gestures() +{ + return _gestures; +} + +std::shared_ptr GestureAction::Config::noneAction() +{ + return _none_action; +} \ No newline at end of file diff --git a/src/logid/actions/GestureAction.h b/src/logid/actions/GestureAction.h new file mode 100644 index 0000000..3928f75 --- /dev/null +++ b/src/logid/actions/GestureAction.h @@ -0,0 +1,67 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_GESTUREACTION_H +#define LOGID_ACTION_GESTUREACTION_H + +#include +#include +#include "Action.h" +#include "gesture/Gesture.h" + +namespace logid { +namespace actions { + class GestureAction : public Action + { + public: + enum Direction + { + None, + Up, + Down, + Left, + Right + }; + static Direction toDirection(std::string direction); + static Direction toDirection(int16_t x, int16_t y); + + GestureAction(Device* dev, libconfig::Setting& config); + + virtual void press(); + virtual void release(); + virtual void move(int16_t x, int16_t y); + + virtual uint8_t reprogFlags() const; + + class Config : public Action::Config + { + public: + Config(Device* device, libconfig::Setting& root); + std::map>& gestures(); + std::shared_ptr noneAction(); + protected: + std::map> _gestures; + std::shared_ptr _none_action; + }; + + protected: + int16_t _x, _y; + Config _config; + }; +}} + +#endif //LOGID_ACTION_GESTUREACTION_H diff --git a/src/logid/actions/KeypressAction.cpp b/src/logid/actions/KeypressAction.cpp new file mode 100644 index 0000000..da097cc --- /dev/null +++ b/src/logid/actions/KeypressAction.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "KeypressAction.h" +#include "../util/log.h" +#include "../InputDevice.h" +#include "../backend/hidpp20/features/ReprogControls.h" + +using namespace logid::actions; +using namespace logid::backend; + +KeypressAction::KeypressAction(Device *device, libconfig::Setting& config) : + Action(device), _config (device, config) +{ +} + +void KeypressAction::press() +{ + _pressed = true; + for(auto& key : _config.keys()) + virtual_input->pressKey(key); +} + +void KeypressAction::release() +{ + _pressed = false; + for(auto& key : _config.keys()) + virtual_input->releaseKey(key); +} + +uint8_t KeypressAction::reprogFlags() const +{ + return hidpp20::ReprogControls::TemporaryDiverted; +} + +KeypressAction::Config::Config(Device* device, libconfig::Setting& config) : + Action::Config(device) +{ + if(!config.isGroup()) { + logPrintf(WARN, "Line %d: action must be an object, skipping.", + config.getSourceLine()); + return; + } + + try { + auto &keys = config.lookup("keys"); + if(keys.isArray() || keys.isList()) { + int key_count = keys.getLength(); + for(int i = 0; i < key_count; i++) { + auto& key = keys[i]; + if(key.isNumber()) { + _keys.push_back(key); + } else if(key.getType() == libconfig::Setting::TypeString) { + try { + _keys.push_back(virtual_input->toKeyCode(key)); + } catch(InputDevice::InvalidEventCode& e) { + logPrintf(WARN, "Line %d: Invalid keycode %s, skipping." + , key.getSourceLine(), key.c_str()); + } + } else { + logPrintf(WARN, "Line %d: keycode must be string or int", + key.getSourceLine(), key.c_str()); + } + } + } + } catch (libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: keys is a required field, skipping.", + config.getSourceLine()); + } +} + +std::vector& KeypressAction::Config::keys() +{ + return _keys; +} \ No newline at end of file diff --git a/src/logid/actions/KeypressAction.h b/src/logid/actions/KeypressAction.h new file mode 100644 index 0000000..4385faf --- /dev/null +++ b/src/logid/actions/KeypressAction.h @@ -0,0 +1,50 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_KEYPRESS_H +#define LOGID_ACTION_KEYPRESS_H + +#include +#include +#include "Action.h" + +namespace logid { +namespace actions { + class KeypressAction : public Action + { + public: + KeypressAction(Device* dev, libconfig::Setting& config); + + virtual void press(); + virtual void release(); + + virtual uint8_t reprogFlags() const; + + class Config : public Action::Config + { + public: + explicit Config(Device* device, libconfig::Setting& root); + std::vector& keys(); + protected: + std::vector _keys; + }; + protected: + Config _config; + }; +}} + +#endif //LOGID_ACTION_KEYPRESS_H diff --git a/src/logid/actions/NullAction.cpp b/src/logid/actions/NullAction.cpp new file mode 100644 index 0000000..8cf875f --- /dev/null +++ b/src/logid/actions/NullAction.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "NullAction.h" +#include "../Device.h" +#include "../backend/hidpp20/features/ReprogControls.h" + +using namespace logid::actions; + +NullAction::NullAction(Device* device) : Action(device) +{ +} + +void NullAction::press() +{ + _pressed = true; +} + +void NullAction::release() +{ + _pressed = false; +} + +uint8_t NullAction::reprogFlags() const +{ + return backend::hidpp20::ReprogControls::TemporaryDiverted; +} \ No newline at end of file diff --git a/src/logid/actions/NullAction.h b/src/logid/actions/NullAction.h new file mode 100644 index 0000000..2d2d0ef --- /dev/null +++ b/src/logid/actions/NullAction.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_NULL_H +#define LOGID_ACTION_NULL_H + +#include "Action.h" + +namespace logid { +namespace actions +{ + class NullAction : public Action + { + public: + explicit NullAction(Device* device); + + virtual void press(); + virtual void release(); + + virtual uint8_t reprogFlags() const; + }; +}} + + +#endif //LOGID_ACTION_NULL_H diff --git a/src/logid/actions/ToggleHiresScroll.cpp b/src/logid/actions/ToggleHiresScroll.cpp new file mode 100644 index 0000000..0c57d54 --- /dev/null +++ b/src/logid/actions/ToggleHiresScroll.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "ToggleHiresScroll.h" +#include "../Device.h" +#include "../util/task.h" +#include "../backend/hidpp20/features/ReprogControls.h" + +using namespace logid::actions; +using namespace logid::backend; + +ToggleHiresScroll::ToggleHiresScroll(Device *dev) : Action (dev) +{ + _hires_scroll = _device->getFeature("hiresscroll"); + if(!_hires_scroll) + logPrintf(WARN, "%s:%d: HiresScroll feature not found, cannot use " + "ToggleHiresScroll action.", + _device->hidpp20().devicePath().c_str(), + _device->hidpp20().devicePath().c_str()); +} + +void ToggleHiresScroll::press() +{ + _pressed = true; + if(_hires_scroll) + { + task::spawn([hires=this->_hires_scroll](){ + auto mode = hires->getMode(); + mode ^= backend::hidpp20::HiresScroll::HiRes; + hires->setMode(mode); + }); + } +} + +void ToggleHiresScroll::release() +{ + _pressed = false; +} + +uint8_t ToggleHiresScroll::reprogFlags() const +{ + return hidpp20::ReprogControls::TemporaryDiverted; +} \ No newline at end of file diff --git a/src/logid/actions/ToggleHiresScroll.h b/src/logid/actions/ToggleHiresScroll.h new file mode 100644 index 0000000..bd60f71 --- /dev/null +++ b/src/logid/actions/ToggleHiresScroll.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_TOGGLEHIRESSCROLL_H +#define LOGID_ACTION_TOGGLEHIRESSCROLL_H + +#include "Action.h" +#include "../features/HiresScroll.h" + +namespace logid { +namespace actions +{ + class ToggleHiresScroll : public Action + { + public: + explicit ToggleHiresScroll(Device* dev); + + virtual void press(); + virtual void release(); + + virtual uint8_t reprogFlags() const; + protected: + std::shared_ptr _hires_scroll; + }; +}} + +#endif //LOGID_ACTION_TOGGLEHIRESSCROLL_H diff --git a/src/logid/actions/ToggleSmartShift.cpp b/src/logid/actions/ToggleSmartShift.cpp new file mode 100644 index 0000000..5e4fd6e --- /dev/null +++ b/src/logid/actions/ToggleSmartShift.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "ToggleSmartShift.h" +#include "../Device.h" +#include "../backend/hidpp20/features/ReprogControls.h" +#include "../util/task.h" + +using namespace logid::actions; +using namespace logid::backend; + +ToggleSmartShift::ToggleSmartShift(Device *dev) : Action (dev) +{ + _smartshift = _device->getFeature("smartshift"); + if(!_smartshift) + logPrintf(WARN, "%s:%d: SmartShift feature not found, cannot use " + "ToggleSmartShift action.", + _device->hidpp20().devicePath().c_str(), + _device->hidpp20().deviceIndex()); +} + +void ToggleSmartShift::press() +{ + _pressed = true; + if(_smartshift) { + task::spawn([ss=this->_smartshift](){ + auto status = ss->getStatus(); + status.setActive = true; + status.active = !status.active; + ss->setStatus(status); + }); + } +} + +void ToggleSmartShift::release() +{ + _pressed = false; +} + +uint8_t ToggleSmartShift::reprogFlags() const +{ + return hidpp20::ReprogControls::TemporaryDiverted; +} \ No newline at end of file diff --git a/src/logid/actions/ToggleSmartShift.h b/src/logid/actions/ToggleSmartShift.h new file mode 100644 index 0000000..7884f48 --- /dev/null +++ b/src/logid/actions/ToggleSmartShift.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_TOGGLESMARTSHIFT_H +#define LOGID_ACTION_TOGGLESMARTSHIFT_H + +#include +#include "Action.h" +#include "../features/SmartShift.h" + +namespace logid { +namespace actions { + class ToggleSmartShift : public Action + { + public: + explicit ToggleSmartShift(Device* dev); + + virtual void press(); + virtual void release(); + + virtual uint8_t reprogFlags() const; + protected: + std::shared_ptr _smartshift; + }; +}} + +#endif //LOGID_ACTION_TOGGLESMARTSHIFT_H \ No newline at end of file diff --git a/src/logid/actions/gesture/AxisGesture.cpp b/src/logid/actions/gesture/AxisGesture.cpp new file mode 100644 index 0000000..615c893 --- /dev/null +++ b/src/logid/actions/gesture/AxisGesture.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "AxisGesture.h" +#include "../../InputDevice.h" +#include "../../util/log.h" + +using namespace logid::actions; + +AxisGesture::AxisGesture(Device *device, libconfig::Setting &root) : + Gesture (device), _config (device, root) +{ +} + +void AxisGesture::press() +{ + _axis = 0; + _axis_remainder = 0; +} + +void AxisGesture::release(bool primary) +{ + // Do nothing + (void)primary; // Suppress unused warning +} + +void AxisGesture::move(int16_t axis) +{ + int16_t new_axis = _axis + axis; + if(new_axis > _config.threshold()) { + double move = axis; + if(_axis < _config.threshold()) + move = new_axis - _config.threshold(); + bool negative_multiplier = _config.multiplier() < 0; + if(negative_multiplier) + move *= -_config.multiplier(); + else + move *= _config.multiplier(); + + double move_floor = floor(move); + _axis_remainder = move - move_floor; + if(_axis_remainder >= 1) { + double int_remainder = floor(_axis_remainder); + move_floor += int_remainder; + _axis_remainder -= int_remainder; + } + + if(negative_multiplier) + move_floor = -move_floor; + + virtual_input->moveAxis(_config.axis(), move_floor); + } + _axis = new_axis; +} + +bool AxisGesture::metThreshold() const +{ + return _axis >= _config.threshold(); +} + +AxisGesture::Config::Config(Device *device, libconfig::Setting &setting) : + Gesture::Config(device, setting, false) +{ + try { + auto& axis = setting.lookup("axis"); + if(axis.isNumber()) { + _axis = axis; + } else if(axis.getType() == libconfig::Setting::TypeString) { + try { + _axis = virtual_input->toAxisCode(axis); + } catch(InputDevice::InvalidEventCode& e) { + logPrintf(WARN, "Line %d: Invalid axis %s, skipping." + , axis.getSourceLine(), axis.c_str()); + } + } else { + logPrintf(WARN, "Line %d: axis must be string or int, skipping.", + axis.getSourceLine(), axis.c_str()); + throw InvalidGesture(); + } + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: axis is a required field, skippimg.", + setting.getSourceLine()); + throw InvalidGesture(); + } + + try { + auto& multiplier = setting.lookup("axis_multiplier"); + if(multiplier.isNumber()) { + if(multiplier.getType() == libconfig::Setting::TypeFloat) + _multiplier = multiplier; + else + _multiplier = (int)multiplier; + } else { + logPrintf(WARN, "Line %d: axis_multiplier must be a number, " + "setting to default (1).", + multiplier.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { + // Ignore + } +} + +unsigned int AxisGesture::Config::axis() const +{ + return _axis; +} + +double AxisGesture::Config::multiplier() const +{ + return _multiplier; +} \ No newline at end of file diff --git a/src/logid/actions/gesture/AxisGesture.h b/src/logid/actions/gesture/AxisGesture.h new file mode 100644 index 0000000..2b69d41 --- /dev/null +++ b/src/logid/actions/gesture/AxisGesture.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_AXISGESTURE_H +#define LOGID_ACTION_AXISGESTURE_H + +#include "Gesture.h" + +namespace logid { + namespace actions + { + class AxisGesture : public Gesture + { + public: + AxisGesture(Device* device, libconfig::Setting& root); + + virtual void press(); + virtual void release(bool primary=false); + virtual void move(int16_t axis); + + virtual bool metThreshold() const; + + class Config : public Gesture::Config + { + public: + Config(Device* device, libconfig::Setting& setting); + unsigned int axis() const; + double multiplier() const; + private: + unsigned int _axis; + double _multiplier = 1; + }; + + protected: + int16_t _axis; + double _axis_remainder; + Config _config; + }; + }} + +#endif //LOGID_ACTION_AXISGESTURE_H diff --git a/src/logid/actions/gesture/Gesture.cpp b/src/logid/actions/gesture/Gesture.cpp new file mode 100644 index 0000000..92a21c8 --- /dev/null +++ b/src/logid/actions/gesture/Gesture.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "Gesture.h" +#include "../../util/log.h" +#include "ReleaseGesture.h" +#include "../../backend/hidpp20/features/ReprogControls.h" +#include "IntervalGesture.h" +#include "AxisGesture.h" +#include "NullGesture.h" + +using namespace logid::actions; + +Gesture::Gesture(Device *device) : _device (device) +{ +} + +Gesture::Config::Config(Device* device, libconfig::Setting& root, + bool action_required) : _device (device) +{ + if(action_required) { + try { + _action = Action::makeAction(_device, + root.lookup("action")); + } catch (libconfig::SettingNotFoundException &e) { + throw InvalidGesture("action is missing"); + } + + if(_action->reprogFlags() & backend::hidpp20::ReprogControls::RawXYDiverted) + throw InvalidGesture("gesture cannot require RawXY"); + } + + _threshold = LOGID_GESTURE_DEFAULT_THRESHOLD; + try { + auto& threshold = root.lookup("threshold"); + if(threshold.getType() == libconfig::Setting::TypeInt) { + _threshold = (int)threshold; + if(_threshold <= 0) { + _threshold = LOGID_GESTURE_DEFAULT_THRESHOLD; + logPrintf(WARN, "Line %d: threshold must be positive, setting " + "to default (%d)", threshold.getSourceLine(), + _threshold); + } + } else + logPrintf(WARN, "Line %d: threshold must be an integer, setting " + "to default (%d).", threshold.getSourceLine()); + } catch(libconfig::SettingNotFoundException& e) { + // Ignore + } +} + +std::shared_ptr Gesture::makeGesture(Device *device, + libconfig::Setting &setting) +{ + if(!setting.isGroup()) { + logPrintf(WARN, "Line %d: Gesture is not a group, ignoring.", + setting.getSourceLine()); + throw InvalidGesture(); + } + + try { + auto& gesture_mode = setting.lookup("mode"); + + if(gesture_mode.getType() != libconfig::Setting::TypeString) { + logPrintf(WARN, "Line %d: Gesture mode must be a string," + "defaulting to OnRelease.", + gesture_mode.getSourceLine()); + return std::make_shared(device, setting); + } + + std::string type = gesture_mode; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); + + if(type == "onrelease") + return std::make_shared(device, setting); + else if(type == "oninterval" || type == "onfewpixels") + return std::make_shared(device, setting); + else if(type == "axis") + return std::make_shared(device, setting); + else if(type == "nopress") + return std::make_shared(device, setting); + else { + logPrintf(WARN, "Line %d: Unknown gesture mode %s, defaulting to " + "OnRelease.", gesture_mode.getSourceLine(), + (const char*)gesture_mode); + return std::make_shared(device, setting); + } + + } catch(libconfig::SettingNotFoundException& e) { + return std::make_shared(device, setting); + } +} + +int16_t Gesture::Config::threshold() const +{ + return _threshold; +} + +std::shared_ptr Gesture::Config::action() +{ + return _action; +} \ No newline at end of file diff --git a/src/logid/actions/gesture/Gesture.h b/src/logid/actions/gesture/Gesture.h new file mode 100644 index 0000000..731c50f --- /dev/null +++ b/src/logid/actions/gesture/Gesture.h @@ -0,0 +1,73 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_GESTURE_H +#define LOGID_ACTION_GESTURE_H + +#include "../Action.h" + +#define LOGID_GESTURE_DEFAULT_THRESHOLD 50 + +namespace logid { +namespace actions +{ + class InvalidGesture : public std::exception + { + public: + explicit InvalidGesture(std::string what="") : _what (what) + { + } + virtual const char* what() + { + return _what.c_str(); + } + private: + std::string _what; + }; + + class Gesture + { + public: + virtual void press() = 0; + virtual void release(bool primary=false) = 0; + virtual void move(int16_t axis) = 0; + + virtual bool metThreshold() const = 0; + + class Config + { + public: + Config(Device* device, libconfig::Setting& root, + bool action_required=true); + virtual int16_t threshold() const; + virtual std::shared_ptr action(); + protected: + Device* _device; + std::shared_ptr _action; + int16_t _threshold; + }; + + static std::shared_ptr makeGesture(Device* device, + libconfig::Setting& setting); + + protected: + explicit Gesture(Device* device); + Device* _device; + }; +}} + +#endif //LOGID_ACTION_GESTURE_H diff --git a/src/logid/actions/gesture/IntervalGesture.cpp b/src/logid/actions/gesture/IntervalGesture.cpp new file mode 100644 index 0000000..355c230 --- /dev/null +++ b/src/logid/actions/gesture/IntervalGesture.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "IntervalGesture.h" +#include "../../util/log.h" + +using namespace logid::actions; + +IntervalGesture::IntervalGesture(Device *device, libconfig::Setting &root) : + Gesture (device), _config (device, root) +{ +} + +void IntervalGesture::press() +{ + _axis = 0; + _interval_pass_count = 0; +} + +void IntervalGesture::release(bool primary) +{ + // Do nothing + (void)primary; // Suppress unused warning +} + +void IntervalGesture::move(int16_t axis) +{ + _axis += axis; + if(_axis < _config.threshold()) + return; + + int16_t new_interval_count = (_axis - _config.threshold())/ + _config.interval(); + if(new_interval_count > _interval_pass_count) { + _config.action()->press(); + _config.action()->release(); + } + _interval_pass_count = new_interval_count; +} + +bool IntervalGesture::metThreshold() const +{ + return _axis >= _config.threshold(); +} + +IntervalGesture::Config::Config(Device *device, libconfig::Setting &setting) : + Gesture::Config(device, setting) +{ + try { + auto& interval = setting.lookup("interval"); + if(interval.getType() != libconfig::Setting::TypeInt) { + logPrintf(WARN, "Line %d: interval must be an integer, skipping.", + interval.getSourceLine()); + throw InvalidGesture(); + } + _interval = (int)interval; + } catch(libconfig::SettingNotFoundException& e) { + try { + // pixels is an alias for interval + auto& interval = setting.lookup("pixels"); + if(interval.getType() != libconfig::Setting::TypeInt) { + logPrintf(WARN, "Line %d: pixels must be an integer, skipping.", + interval.getSourceLine()); + throw InvalidGesture(); + } + _interval = (int)interval; + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: interval is a required field, skipping.", + setting.getSourceLine()); + } + } +} + +int16_t IntervalGesture::Config::interval() const +{ + return _interval; +} \ No newline at end of file diff --git a/src/logid/actions/gesture/IntervalGesture.h b/src/logid/actions/gesture/IntervalGesture.h new file mode 100644 index 0000000..29dcb47 --- /dev/null +++ b/src/logid/actions/gesture/IntervalGesture.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_INTERVALGESTURE_H +#define LOGID_ACTION_INTERVALGESTURE_H + +#include "Gesture.h" + +namespace logid { +namespace actions +{ + class IntervalGesture : public Gesture + { + public: + IntervalGesture(Device* device, libconfig::Setting& root); + + virtual void press(); + virtual void release(bool primary=false); + virtual void move(int16_t axis); + + virtual bool metThreshold() const; + + class Config : public Gesture::Config + { + public: + Config(Device* device, libconfig::Setting& setting); + int16_t interval() const; + private: + int16_t _interval; + }; + + protected: + int16_t _axis; + int16_t _interval_pass_count; + Config _config; + }; +}} + +#endif //LOGID_ACTION_INTERVALGESTURE_H diff --git a/src/logid/actions/gesture/NullGesture.cpp b/src/logid/actions/gesture/NullGesture.cpp new file mode 100644 index 0000000..c97b45d --- /dev/null +++ b/src/logid/actions/gesture/NullGesture.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "NullGesture.h" + +using namespace logid::actions; + +NullGesture::NullGesture(Device *device, libconfig::Setting& setting) : + Gesture (device), _config (device, setting, false) +{ +} + +void NullGesture::press() +{ + _axis = 0; +} + +void NullGesture::release(bool primary) +{ + // Do nothing + (void)primary; // Suppress unused warning +} + +void NullGesture::move(int16_t axis) +{ + _axis += axis; +} + +bool NullGesture::metThreshold() const +{ + return _axis > _config.threshold(); +} \ No newline at end of file diff --git a/src/logid/actions/gesture/NullGesture.h b/src/logid/actions/gesture/NullGesture.h new file mode 100644 index 0000000..9ea63c5 --- /dev/null +++ b/src/logid/actions/gesture/NullGesture.h @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_NULLGESTURE_H +#define LOGID_ACTION_NULLGESTURE_H + +#include "Gesture.h" + +namespace logid { +namespace actions +{ + class NullGesture : public Gesture + { + public: + NullGesture(Device* device, libconfig::Setting& setting); + + virtual void press(); + virtual void release(bool primary=false); + virtual void move(int16_t axis); + + virtual bool metThreshold() const; + protected: + int16_t _axis; + Gesture::Config _config; + }; +}} + +#endif //LOGID_ACTION_NULLGESTURE_H diff --git a/src/logid/actions/gesture/ReleaseGesture.cpp b/src/logid/actions/gesture/ReleaseGesture.cpp new file mode 100644 index 0000000..52b3ea1 --- /dev/null +++ b/src/logid/actions/gesture/ReleaseGesture.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "ReleaseGesture.h" + +using namespace logid::actions; + +ReleaseGesture::ReleaseGesture(Device *device, libconfig::Setting &root) : + Gesture (device), _config (device, root) +{ +} + +void ReleaseGesture::press() +{ + _axis = 0; +} + +void ReleaseGesture::release(bool primary) +{ + if(metThreshold() && primary) { + _config.action()->press(); + _config.action()->release(); + } +} + +void ReleaseGesture::move(int16_t axis) +{ + _axis += axis; +} + +bool ReleaseGesture::metThreshold() const +{ + return _axis >= _config.threshold(); +} \ No newline at end of file diff --git a/src/logid/actions/gesture/ReleaseGesture.h b/src/logid/actions/gesture/ReleaseGesture.h new file mode 100644 index 0000000..b35fda2 --- /dev/null +++ b/src/logid/actions/gesture/ReleaseGesture.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_ACTION_RELEASEGESTURE_H +#define LOGID_ACTION_RELEASEGESTURE_H + +#include "Gesture.h" + +namespace logid { +namespace actions +{ + class ReleaseGesture : public Gesture + { + public: + ReleaseGesture(Device* device, libconfig::Setting& root); + + virtual void press(); + virtual void release(bool primary=false); + virtual void move(int16_t axis); + + virtual bool metThreshold() const; + + protected: + int16_t _axis; + Gesture::Config _config; + }; +}} + +#endif //LOGID_ACTION_RELEASEGESTURE_H diff --git a/src/logid/backend/Error.cpp b/src/logid/backend/Error.cpp new file mode 100644 index 0000000..f15fc0e --- /dev/null +++ b/src/logid/backend/Error.cpp @@ -0,0 +1,24 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "Error.h" + +const char *logid::backend::TimeoutError::what() const noexcept +{ + return "Device timed out"; +} diff --git a/src/logid/backend/Error.h b/src/logid/backend/Error.h new file mode 100644 index 0000000..d281a3b --- /dev/null +++ b/src/logid/backend/Error.h @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_ERROR_H +#define LOGID_BACKEND_ERROR_H + +#include + +namespace logid { +namespace backend { +class TimeoutError: public std::exception +{ +public: + TimeoutError() = default; + const char* what() const noexcept override; +}; +}} + +#endif //LOGID_BACKEND_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/dj/Error.cpp b/src/logid/backend/dj/Error.cpp new file mode 100644 index 0000000..4277cf6 --- /dev/null +++ b/src/logid/backend/dj/Error.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "Error.h" + +using namespace logid::backend::dj; + +Error::Error(uint8_t code) : _code (code) +{ +} + +const char* Error::what() const noexcept +{ + switch(_code) { + case Unknown: + return "Unknown"; + case KeepAliveTimeout: + return "Keep-alive timeout"; + default: + return "Reserved"; + } +} + +uint8_t Error::code() const noexcept +{ + return _code; +} \ No newline at end of file diff --git a/src/logid/backend/dj/Error.h b/src/logid/backend/dj/Error.h new file mode 100644 index 0000000..dc44067 --- /dev/null +++ b/src/logid/backend/dj/Error.h @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_DJ_ERROR_H +#define LOGID_BACKEND_DJ_ERROR_H + +#include +#include + +namespace logid { +namespace backend { +namespace dj +{ + class Error : public std::exception + { + public: + enum ErrorCode : uint8_t + { + Unknown = 0x00, + KeepAliveTimeout = 0x01 + }; + + explicit Error(uint8_t code); + + const char* what() const noexcept override; + uint8_t code() const noexcept; + + private: + uint8_t _code; + }; +}}} + +#endif //LOGID_BACKEND_DJ_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/dj/Receiver.cpp b/src/logid/backend/dj/Receiver.cpp new file mode 100644 index 0000000..78909da --- /dev/null +++ b/src/logid/backend/dj/Receiver.cpp @@ -0,0 +1,383 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "Report.h" +#include "Receiver.h" +#include "Error.h" +#include "../../util/thread.h" + +using namespace logid::backend::dj; +using namespace logid::backend; + +InvalidReceiver::InvalidReceiver(Reason reason) : _reason (reason) +{ +} + +const char* InvalidReceiver::what() const noexcept +{ + switch(_reason) { + case NoDJReports: + return "No DJ reports"; + default: + return "Invalid receiver"; + } +} + +InvalidReceiver::Reason InvalidReceiver::code() const noexcept +{ + return _reason; +} + +Receiver::Receiver(std::string path) : + _raw_device (std::make_shared(std::move(path))), + _hidpp10_device (_raw_device, hidpp::DefaultDevice) +{ + if(!supportsDjReports(_raw_device->reportDescriptor())) + throw InvalidReceiver(InvalidReceiver::NoDJReports); +} + +void Receiver::enumerateDj() +{ + _sendDjRequest(hidpp::DefaultDevice, GetPairedDevices,{}); +} + +Receiver::NotificationFlags Receiver::getHidppNotifications() +{ + auto response = _hidpp10_device.getRegister(EnableHidppNotifications, {}, + hidpp::ReportType::Short); + + NotificationFlags flags{}; + flags.deviceBatteryStatus = response[0] & (1 << 4); + flags.receiverWirelessNotifications = response[1] & (1 << 0); + flags.receiverSoftwarePresent = response[1] & (1 << 3); + + return flags; +} + +void Receiver::enableHidppNotifications(NotificationFlags flags) +{ + std::vector request(3); + + if(flags.deviceBatteryStatus) + request[0] |= (1 << 4); + if(flags.receiverWirelessNotifications) + request[1] |= 1; + if(flags.receiverSoftwarePresent) + request[1] |= (1 << 3); + + _hidpp10_device.setRegister(EnableHidppNotifications, request, + hidpp::ReportType::Short); +} + +void Receiver::enumerateHidpp() +{ + /* This isn't in the documentation but this is how solaar does it + * All I know is that when (p0 & 2), devices are enumerated + */ + _hidpp10_device.setRegister(ConnectionState, {2}, + hidpp::ReportType::Short); +} + +///TODO: Investigate usage +uint8_t Receiver::getConnectionState(hidpp::DeviceIndex index) +{ + auto response = _hidpp10_device.getRegister(ConnectionState, {index}, + hidpp::ReportType::Short); + + return response[0]; +} + +void Receiver::startPairing(uint8_t timeout) +{ + ///TODO: Device number == Device index? + std::vector request(3); + + request[0] = 1; + request[1] = hidpp::DefaultDevice; + request[2] = timeout; + + _hidpp10_device.setRegister(DevicePairing, request, + hidpp::ReportType::Short); +} + +void Receiver::stopPairing() +{ + ///TODO: Device number == Device index? + std::vector request(3); + + request[0] = 2; + request[1] = hidpp::DefaultDevice; + + _hidpp10_device.setRegister(DevicePairing, request, + hidpp::ReportType::Short); +} + +void Receiver::disconnect(hidpp::DeviceIndex index) +{ + ///TODO: Device number == Device index? + std::vector request(3); + + request[0] = 3; + request[1] = index; + + _hidpp10_device.setRegister(DevicePairing, request, + hidpp::ReportType::Short); +} + +std::map Receiver::getDeviceActivity() +{ + auto response = _hidpp10_device.getRegister(DeviceActivity, {}, + hidpp::ReportType::Long); + + std::map device_activity; + for(uint8_t i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; i++) + device_activity[static_cast(i)] = response[i]; + + return device_activity; +} + +struct Receiver::PairingInfo + Receiver::getPairingInfo(hidpp::DeviceIndex index) +{ + std::vector request(1); + request[0] = index; + request[0] += 0x1f; + + auto response = _hidpp10_device.getRegister(PairingInfo, request, + hidpp::ReportType::Long); + + struct PairingInfo info{}; + info.destinationId = response[1]; + info.reportInterval = response[2]; + info.pid = response[4]; + info.pid |= (response[3] << 8); + info.deviceType = static_cast(response[7]); + + return info; +} + +struct Receiver::ExtendedPairingInfo + Receiver::getExtendedPairingInfo(hidpp::DeviceIndex index) +{ + std::vector request(1); + request[0] = index; + request[0] += 0x2f; + + auto response = _hidpp10_device.getRegister(PairingInfo, request, + hidpp::ReportType::Long); + + ExtendedPairingInfo info{}; + + info.serialNumber = 0; + for(uint8_t i = 0; i < 4; i++) + info.serialNumber |= (response[i+1] << 8*i); + + for(uint8_t i = 0; i < 4; i++) + info.reportTypes[i] = response[i + 5]; + + uint8_t psl = response[8] & 0xf; + if(psl > 0xc) + info.powerSwitchLocation = PowerSwitchLocation::Reserved; + else + info.powerSwitchLocation = static_cast(psl); + + return info; +} + +std::string Receiver::getDeviceName(hidpp::DeviceIndex index) +{ + std::vector request(1); + request[0] = index; + request[0] += 0x3f; + + auto response = _hidpp10_device.getRegister(PairingInfo, request, + hidpp::ReportType::Long); + + uint8_t size = response[1]; + assert(size <= 14); + + std::string name(size, ' '); + for(std::size_t i = 0; i < size; i++) + name[i] = response[i + 2]; + + return name; +} + +hidpp::DeviceIndex Receiver::deviceDisconnectionEvent(const hidpp::Report& +report) +{ + assert(report.subId() == DeviceDisconnection); + return report.deviceIndex(); +} + +hidpp::DeviceConnectionEvent Receiver::deviceConnectionEvent(const + hidpp::Report &report) +{ + assert(report.subId() == DeviceConnection); + + hidpp::DeviceConnectionEvent event{}; + + event.index = report.deviceIndex(); + event.unifying = ((report.address() & 0b111) == 0x04); + + event.deviceType = static_cast( + report.paramBegin()[0] & 0x0f); + event.softwarePresent = report.paramBegin()[0] & (1<<4); + event.encrypted = report.paramBegin()[0] & (1<<5); + event.linkEstablished = !(report.paramBegin()[0] & (1<<6)); + event.withPayload = report.paramBegin()[0] & (1<<7); + event.fromTimeoutCheck = false; + + event.pid =(report.paramBegin()[2] << 8); + event.pid |= report.paramBegin()[1]; + + return event; +} + +void Receiver::_handleDjEvent(Report& report) +{ + for(auto& handler : _dj_event_handlers) + if(handler.second->condition(report)) + handler.second->callback(report); +} + +void Receiver::_handleHidppEvent(hidpp::Report &report) +{ + for(auto& handler : _hidpp_event_handlers) + if(handler.second->condition(report)) + handler.second->callback(report); +} + +void Receiver::addDjEventHandler(const std::string& nickname, + const std::shared_ptr& handler) +{ + auto it = _dj_event_handlers.find(nickname); + assert(it == _dj_event_handlers.end()); + _dj_event_handlers.emplace(nickname, handler); +} + +void Receiver::removeDjEventHandler(const std::string &nickname) +{ + _dj_event_handlers.erase(nickname); +} + +const std::map>& +Receiver::djEventHandlers() +{ + return _dj_event_handlers; +} + +void Receiver::addHidppEventHandler(const std::string& nickname, + const std::shared_ptr& handler) +{ + auto it = _hidpp_event_handlers.find(nickname); + assert(it == _hidpp_event_handlers.end()); + _hidpp_event_handlers.emplace(nickname, handler); +} + +void Receiver::removeHidppEventHandler(const std::string &nickname) +{ + _hidpp_event_handlers.erase(nickname); +} + +const std::map>& +Receiver::hidppEventHandlers() +{ + return _hidpp_event_handlers; +} + +void Receiver::_sendDjRequest(hidpp::DeviceIndex index, uint8_t function, + const std::vector&& params) +{ + assert(params.size() <= LongParamLength); + + Report::Type type = params.size() <= ShortParamLength ? + ReportType::Short : ReportType::Long; + + Report request(type, index, function); + + std::copy(params.begin(), params.end(), request.paramBegin()); + + _raw_device->sendReportNoResponse(request.rawData()); +} + +Receiver::ConnectionStatusEvent Receiver::connectionStatusEvent(Report& report) +{ + assert(report.feature() == ConnectionStatus); + ConnectionStatusEvent event{}; + event.index = report.index(); + event.linkLost = report.paramBegin()[0]; + return event; +} + +void Receiver::listen() +{ + if(!_raw_device->isListening()) + _raw_device->listenAsync(); + + if(_raw_device->eventHandlers().find("RECV_HIDPP") == + _raw_device->eventHandlers().end()) { + // Pass all HID++ events on DefaultDevice to handleHidppEvent + std::shared_ptr hidpp_handler = + std::make_shared(); + hidpp_handler->condition = [](std::vector& report)->bool + { + return (report[hidpp::Offset::Type] == hidpp::Report::Type::Short || + report[hidpp::Offset::Type] == hidpp::Report::Type::Long); + }; + hidpp_handler->callback = [this](std::vector& report) + ->void { + hidpp::Report _report(report); + this->_handleHidppEvent(_report); + }; + _raw_device->addEventHandler("RECV_HIDPP", hidpp_handler); + } + + if(_raw_device->eventHandlers().find("RECV_DJ") == + _raw_device->eventHandlers().end()) { + // Pass all DJ events with device index to handleDjEvent + std::shared_ptr dj_handler = + std::make_shared(); + dj_handler->condition = [](std::vector& report)->bool + { + return (report[Offset::Type] == Report::Type::Short || + report[Offset::Type] == Report::Type::Long); + }; + dj_handler->callback = [this](std::vector& report)->void + { + Report _report(report); + this->_handleDjEvent(_report); + }; + _raw_device->addEventHandler("RECV_DJ", dj_handler); + } +} + +void Receiver::stopListening() +{ + _raw_device->removeEventHandler("RECV_HIDPP"); + _raw_device->removeEventHandler("RECV_DJ"); + + if(_raw_device->eventHandlers().empty()) + _raw_device->stopListener(); +} + +std::shared_ptr Receiver::rawDevice() const +{ + return _raw_device; +} \ No newline at end of file diff --git a/src/logid/backend/dj/Receiver.h b/src/logid/backend/dj/Receiver.h new file mode 100644 index 0000000..96b74db --- /dev/null +++ b/src/logid/backend/dj/Receiver.h @@ -0,0 +1,213 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_DJ_RECEIVER_H +#define LOGID_BACKEND_DJ_RECEIVER_H + +#include +#include "../raw/RawDevice.h" +#include "Report.h" +#include "../hidpp/Report.h" +#include "../hidpp10/Device.h" + +namespace logid { +namespace backend { +namespace dj +{ + struct EventHandler + { + std::function condition; + std::function callback; + }; + + class InvalidReceiver : public std::exception + { + public: + enum Reason + { + NoDJReports + }; + explicit InvalidReceiver(Reason reason); + const char* what() const noexcept override; + Reason code() const noexcept; + private: + Reason _reason; + }; + + class Receiver + { + public: + explicit Receiver(std::string path); + + enum DjEvents : uint8_t + { + DeviceDisconnection = 0x40, + DeviceConnection = 0x41, + ConnectionStatus = 0x42 + }; + + enum DjCommands : uint8_t + { + SwitchAndKeepAlive = 0x80, + GetPairedDevices = 0x81 + }; + + void enumerateDj(); + + struct ConnectionStatusEvent + { + hidpp::DeviceIndex index; + bool linkLost; + }; + + ConnectionStatusEvent connectionStatusEvent(dj::Report& report); + + /* The following functions deal with HID++ 1.0 features. + * While these are not technically DJ functions, it is redundant + * to have a separate hidpp10::Receiver class for these functions. + */ + + enum HidppEvents : uint8_t + { + // These events are identical to their DJ counterparts + // DeviceDisconnection = 0x40, + // DeviceConnection = 0x41, + LockingChange = 0x4a + }; + + enum HidppRegisters : uint8_t + { + EnableHidppNotifications = 0x00, + ConnectionState = 0x02, + DevicePairing = 0xb2, + DeviceActivity = 0xb3, + PairingInfo = 0xb5 + }; + + struct NotificationFlags + { + bool deviceBatteryStatus; + bool receiverWirelessNotifications; + bool receiverSoftwarePresent; + }; + + NotificationFlags getHidppNotifications(); + void enableHidppNotifications(NotificationFlags flags); + + void enumerateHidpp(); + uint8_t getConnectionState(hidpp::DeviceIndex index); + + void startPairing(uint8_t timeout = 0); + void stopPairing(); + void disconnect(hidpp::DeviceIndex index); + + std::map getDeviceActivity(); + + struct PairingInfo + { + uint8_t destinationId; + uint8_t reportInterval; + uint16_t pid; + DeviceType::DeviceType deviceType; + }; + + enum class PowerSwitchLocation : uint8_t + { + Reserved = 0x0, + Base = 0x1, + TopCase = 0x2, + TopRightEdge = 0x3, + Other = 0x4, + TopLeft = 0x5, + BottomLeft = 0x6, + TopRight = 0x7, + BottomRight = 0x8, + TopEdge = 0x9, + RightEdge = 0xa, + LeftEdge = 0xb, + BottomEdge = 0xc + }; + + struct ExtendedPairingInfo + { + uint32_t serialNumber; + uint8_t reportTypes[4]; + PowerSwitchLocation powerSwitchLocation; + }; + + struct PairingInfo getPairingInfo(hidpp::DeviceIndex index); + struct ExtendedPairingInfo getExtendedPairingInfo(hidpp::DeviceIndex + index); + + std::string getDeviceName(hidpp::DeviceIndex index); + + static hidpp::DeviceIndex deviceDisconnectionEvent( + const hidpp::Report& report); + static hidpp::DeviceConnectionEvent deviceConnectionEvent( + const hidpp::Report& report); + + void listen(); + void stopListening(); + + void addDjEventHandler(const std::string& nickname, + const std::shared_ptr& handler); + void removeDjEventHandler(const std::string& nickname); + const std::map>& + djEventHandlers(); + + void addHidppEventHandler(const std::string& nickname, + const std::shared_ptr& handler); + void removeHidppEventHandler(const std::string& nickname); + const std::map>& + hidppEventHandlers(); + + std::shared_ptr rawDevice() const; + private: + void _sendDjRequest(hidpp::DeviceIndex index, uint8_t function, + const std::vector&& params); + + void _handleDjEvent(dj::Report& report); + void _handleHidppEvent(hidpp::Report& report); + + std::map> + _dj_event_handlers; + std::map> + _hidpp_event_handlers; + + std::shared_ptr _raw_device; + hidpp10::Device _hidpp10_device; + }; +} + +namespace hidpp +{ + struct DeviceConnectionEvent + { + hidpp::DeviceIndex index; + uint16_t pid; + dj::DeviceType::DeviceType deviceType; + bool unifying; + bool softwarePresent; + bool encrypted; + bool linkEstablished; + bool withPayload; + bool fromTimeoutCheck = false; // Fake field + }; +}}} + +#endif //LOGID_BACKEND_DJ_RECEIVER_H \ No newline at end of file diff --git a/src/logid/backend/dj/ReceiverMonitor.cpp b/src/logid/backend/dj/ReceiverMonitor.cpp new file mode 100644 index 0000000..80a9283 --- /dev/null +++ b/src/logid/backend/dj/ReceiverMonitor.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "ReceiverMonitor.h" +#include "../../util/task.h" +#include "../../util/log.h" + +#include +#include + +using namespace logid::backend::dj; + +ReceiverMonitor::ReceiverMonitor(std::string path) : _receiver ( + std::make_shared(std::move(path))) +{ + assert(_receiver->hidppEventHandlers().find("RECVMON") == + _receiver->hidppEventHandlers().end()); + assert(_receiver->djEventHandlers().find("RECVMON") == + _receiver->djEventHandlers().end()); + + Receiver::NotificationFlags notification_flags{ + true, + true, + true}; + _receiver->enableHidppNotifications(notification_flags); +} + +ReceiverMonitor::~ReceiverMonitor() +{ + this->stop(); +} + +void ReceiverMonitor::run() +{ + _receiver->listen(); + + if(_receiver->hidppEventHandlers().find("RECVMON") == + _receiver->hidppEventHandlers().end()) { + std::shared_ptr event_handler = + std::make_shared(); + event_handler->condition = [](hidpp::Report &report) -> bool { + return (report.subId() == Receiver::DeviceConnection || + report.subId() == Receiver::DeviceDisconnection); + }; + + event_handler->callback = [this](hidpp::Report &report) -> void { + /* Running in a new thread prevents deadlocks since the + * receiver may be enumerating. + */ + task::spawn({[this, report]() { + if (report.subId() == Receiver::DeviceConnection) + this->addDevice(this->_receiver->deviceConnectionEvent + (report)); + else if (report.subId() == Receiver::DeviceDisconnection) + this->removeDevice(this->_receiver-> + deviceDisconnectionEvent(report)); + }}, {[report, path=this->_receiver->rawDevice()->hidrawPath()] + (std::exception& e) { + if(report.subId() == Receiver::DeviceConnection) + logPrintf(ERROR, "Failed to add device %d to receiver " + "on %s: %s", report.deviceIndex(), + path.c_str(), e.what()); + else if(report.subId() == Receiver::DeviceDisconnection) + logPrintf(ERROR, "Failed to remove device %d from " + "receiver on %s: %s", report.deviceIndex() + ,path.c_str(), e.what()); + }}); + }; + + _receiver->addHidppEventHandler("RECVMON", event_handler); + } + + enumerate(); +} + +void ReceiverMonitor::stop() +{ + _receiver->removeHidppEventHandler("RECVMON"); + + _receiver->stopListening(); +} + +void ReceiverMonitor::enumerate() +{ + _receiver->enumerateHidpp(); +} + +void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) +{ + std::string nickname = "WAIT_DEV_" + std::to_string(index); + auto handler = std::make_shared(); + handler->condition = [index](std::vector& report)->bool { + return report[Offset::DeviceIndex] == index; + }; + + handler->callback = [this, index, nickname](std::vector& report) { + (void)report; // Suppress unused warning + + hidpp::DeviceConnectionEvent event{}; + event.withPayload = false; + event.linkEstablished = true; + event.index = index; + event.fromTimeoutCheck = true; + + task::spawn({[this, event, nickname]() { + _receiver->rawDevice()->removeEventHandler(nickname); + this->addDevice(event); + }}, {[path=_receiver->rawDevice()->hidrawPath(), event] + (std::exception& e) { + logPrintf(ERROR, "Failed to add device %d to receiver " + "on %s: %s", event.index, + path.c_str(), e.what()); + }}); + }; + + _receiver->rawDevice()->addEventHandler(nickname, handler); +} + +std::shared_ptr ReceiverMonitor::receiver() const +{ + return _receiver; +} \ No newline at end of file diff --git a/src/logid/backend/dj/ReceiverMonitor.h b/src/logid/backend/dj/ReceiverMonitor.h new file mode 100644 index 0000000..b56aced --- /dev/null +++ b/src/logid/backend/dj/ReceiverMonitor.h @@ -0,0 +1,61 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_DJ_RECEIVERMONITOR_H +#define LOGID_BACKEND_DJ_RECEIVERMONITOR_H + +#include +#include +#include "Receiver.h" +#include "../hidpp/defs.h" + +namespace logid { +namespace backend { +namespace dj +{ + // This class will run on the RawDevice thread, + class ReceiverMonitor + { + public: + explicit ReceiverMonitor(std::string path); + ~ReceiverMonitor(); + + void enumerate(); + void run(); + void stop(); + + protected: + virtual void addDevice(hidpp::DeviceConnectionEvent event) = 0; + virtual void removeDevice(hidpp::DeviceIndex index) = 0; + + void waitForDevice(hidpp::DeviceIndex index); + + // Internal methods for derived class + void _pair(uint8_t timeout = 0); + void _stopPairing(); + + void _unpair(); + + std::shared_ptr receiver() const; + private: + std::shared_ptr _receiver; + }; + +}}} + +#endif //LOGID_BACKEND_DJ_RECEIVERMONITOR_H \ No newline at end of file diff --git a/src/logid/backend/dj/Report.cpp b/src/logid/backend/dj/Report.cpp new file mode 100644 index 0000000..dff4040 --- /dev/null +++ b/src/logid/backend/dj/Report.cpp @@ -0,0 +1,135 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include "Report.h" + +using namespace logid::backend::dj; +using namespace logid::backend; + +static const std::array DJReportDesc = { + 0xA1, 0x01, // Collection (Application) + 0x85, 0x20, // Report ID (32) + 0x95, 0x0E, // Report Count (14) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x41, // Usage (0x41) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x41, // Usage (0x41) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0x85, 0x21, // Report ID (33) + 0x95, 0x1F, // Report Count (31) + 0x09, 0x42, // Usage (0x42) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x42, // Usage (0x42) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection +}; + +static const std::array DJReportDesc2 = { + 0xA1, 0x01, // Collection (Application) + 0x85, 0x20, // Report ID (32) + 0x75, 0x08, // Report Size (8) + 0x95, 0x0E, // Report Count (14) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x41, // Usage (0x41) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x41, // Usage (0x41) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0x85, 0x21, // Report ID (33) + 0x95, 0x1F, // Report Count (31) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x42, // Usage (0x42) + 0x81, 0x00, // Input (Data, Array, Absolute) + 0x09, 0x42, // Usage (0x42) + 0x91, 0x00, // Output (Data, Array, Absolute) + 0xC0 // End Collection +}; + +bool dj::supportsDjReports(std::vector&& rdesc) +{ + auto it = std::search(rdesc.begin(), rdesc.end(), + DJReportDesc.begin(), DJReportDesc.end()); + if(it == rdesc.end()) + it = std::search(rdesc.begin(), rdesc.end(), + DJReportDesc2.begin(), DJReportDesc2.end()); + return it != rdesc.end(); +} + +Report::Report(std::vector& data) : _data (data) +{ + switch(data[Offset::Type]) { + case ReportType::Short: + _data.resize(HeaderLength+ShortParamLength); + break; + case ReportType::Long: + _data.resize(HeaderLength+LongParamLength); + break; + default: + assert(false); + } +} + +Report::Report(Report::Type type, hidpp::DeviceIndex index, uint8_t feature) +{ + switch(type) { + case ReportType::Short: + _data.resize(HeaderLength+ShortParamLength); + break; + case ReportType::Long: + _data.resize(HeaderLength+LongParamLength); + break; + default: + assert(false); + } + + _data[Offset::Type] = type; + _data[Offset::DeviceIndex] = index; + _data[Offset::Feature] = feature; +} + + +Report::Type Report::type() const +{ + return static_cast(_data[Offset::Type]); +} + +hidpp::DeviceIndex Report::index() const +{ + return static_cast(_data[Offset::DeviceIndex]); +} + +uint8_t Report::feature() const +{ + return _data[Offset::Feature]; +} + +std::vector::iterator Report::paramBegin() +{ + return _data.begin() + Offset::Parameters; +} + +std::vector Report::rawData() const +{ + return _data; +} diff --git a/src/logid/backend/dj/Report.h b/src/logid/backend/dj/Report.h new file mode 100644 index 0000000..76439a5 --- /dev/null +++ b/src/logid/backend/dj/Report.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_DJ_REPORT_H +#define LOGID_BACKEND_DJ_REPORT_H + +#include +#include "../raw/RawDevice.h" +#include "defs.h" +#include "../hidpp/defs.h" + +namespace logid { +namespace backend { +namespace dj +{ + namespace Offset + { + static constexpr uint8_t Type = 0; + static constexpr uint8_t DeviceIndex = 1; + static constexpr uint8_t Feature = 2; + static constexpr uint8_t Parameters = 3; + } + + bool supportsDjReports(std::vector&& rdesc); + class Report + { + public: + typedef ReportType::ReportType Type; + + explicit Report(std::vector& data); + Report(Type type, hidpp::DeviceIndex index, uint8_t feature); + + Type type() const; + hidpp::DeviceIndex index() const; + uint8_t feature() const; + std::vector::iterator paramBegin(); + std::vector rawData() const; + private: + std::vector _data; + }; +}}} + +#endif //LOGID_BACKEND_DJ_REPORT_H diff --git a/src/logid/backend/dj/defs.h b/src/logid/backend/dj/defs.h new file mode 100644 index 0000000..d527d9b --- /dev/null +++ b/src/logid/backend/dj/defs.h @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_DJ_DEFS_H +#define LOGID_BACKEND_DJ_DEFS_H + +#include + +namespace logid { +namespace backend { +namespace dj +{ + namespace ReportType + { + enum ReportType : uint8_t + { + Short = 0x20, + Long = 0x21 + }; + } + + namespace DeviceType + { + enum DeviceType : uint8_t + { + Unknown = 0x00, + Keyboard = 0x01, + Mouse = 0x02, + Numpad = 0x03, + Presenter = 0x04, + /* 0x05-0x07 is reserved */ + Trackball = 0x08, + Touchpad = 0x09 + }; + } + + static constexpr uint8_t ErrorFeature = 0x7f; + + static constexpr std::size_t HeaderLength = 3; + static constexpr std::size_t ShortParamLength = 12; + static constexpr std::size_t LongParamLength = 29; +}}} + +#endif //LOGID_BACKEND_DJ_DEFS_H \ No newline at end of file diff --git a/src/logid/backend/hidpp/Device.cpp b/src/logid/backend/hidpp/Device.cpp new file mode 100644 index 0000000..d4bf994 --- /dev/null +++ b/src/logid/backend/hidpp/Device.cpp @@ -0,0 +1,234 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include "../../util/thread.h" +#include "Device.h" +#include "Report.h" +#include "../hidpp20/features/Root.h" +#include "../hidpp20/features/DeviceName.h" +#include "../hidpp20/Error.h" +#include "../hidpp10/Error.h" +#include "../dj/Receiver.h" + +using namespace logid::backend; +using namespace logid::backend::hidpp; + +const char* Device::InvalidDevice::what() const noexcept +{ + switch(_reason) { + case NoHIDPPReport: + return "Invalid HID++ device"; + case InvalidRawDevice: + return "Invalid raw device"; + case Asleep: + return "Device asleep"; + default: + return "Invalid device"; + } +} + +Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept +{ + return _reason; +} + +Device::Device(const std::string& path, DeviceIndex index): + _raw_device (std::make_shared(path)), _receiver (nullptr), + _path (path), _index (index) +{ + _init(); +} + +Device::Device(std::shared_ptr raw_device, DeviceIndex index) : + _raw_device (std::move(raw_device)), _receiver (nullptr), + _path (_raw_device->hidrawPath()), _index (index) +{ + _init(); +} + +Device::Device(std::shared_ptr receiver, + hidpp::DeviceConnectionEvent event) : + _raw_device (receiver->rawDevice()), _index (event.index) +{ + // Device will throw an error soon, just do it now + if(!event.linkEstablished) + throw InvalidDevice(InvalidDevice::Asleep); + + if(!event.fromTimeoutCheck) + _pid = event.pid; + else + _pid = receiver->getPairingInfo(_index).pid; + _init(); +} + +std::string Device::devicePath() const +{ + return _path; +} + +DeviceIndex Device::deviceIndex() const +{ + return _index; +} + +std::tuple Device::version() const +{ + return _version; +} + +void Device::_init() +{ + _listening = false; + _supported_reports = getSupportedReports(_raw_device->reportDescriptor()); + if(!_supported_reports) + throw InvalidDevice(InvalidDevice::NoHIDPPReport); + + try { + hidpp20::EssentialRoot root(this); + _version = root.getVersion(); + } catch(hidpp10::Error &e) { + // Valid HID++ 1.0 devices should send an InvalidSubID error + if(e.code() != hidpp10::Error::InvalidSubID) + throw; + + // HID++ 2.0 is not supported, assume HID++ 1.0 + _version = std::make_tuple(1, 0); + } + + if(!_receiver) { + _pid = _raw_device->productId(); + if(std::get<0>(_version) >= 2) { + try { + hidpp20::EssentialDeviceName deviceName(this); + _name = deviceName.getName(); + } catch(hidpp20::UnsupportedFeature &e) { + _name = _raw_device->name(); + } + } else { + _name = _raw_device->name(); + } + } else { + _name = _receiver->getDeviceName(_index); + } +} + +Device::~Device() +{ + if(_listening) + _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); +} + +void Device::addEventHandler(const std::string& nickname, + const std::shared_ptr& handler) +{ + auto it = _event_handlers.find(nickname); + assert(it == _event_handlers.end()); + + _event_handlers.emplace(nickname, handler); +} + +void Device::removeEventHandler(const std::string& nickname) +{ + _event_handlers.erase(nickname); +} + +const std::map>& + Device::eventHandlers() +{ + return _event_handlers; +} + +void Device::handleEvent(Report& report) +{ + for(auto& handler : _event_handlers) + if(handler.second->condition(report)) + handler.second->callback(report); +} + +Report Device::sendReport(Report& report) +{ + switch(report.type()) + { + case Report::Type::Short: + if(!(_supported_reports & HIDPP_REPORT_SHORT_SUPPORTED)) + report.setType(Report::Type::Long); + break; + case Report::Type::Long: + /* Report can be truncated, but that isn't a good idea. */ + assert(_supported_reports & HIDPP_REPORT_LONG_SUPPORTED); + } + + auto raw_response = _raw_device->sendReport(report.rawReport()); + + Report response(raw_response); + + Report::Hidpp10Error hidpp10_error{}; + if(response.isError10(&hidpp10_error)) + throw hidpp10::Error(hidpp10_error.error_code); + + Report::Hidpp20Error hidpp20_error{}; + if(response.isError20(&hidpp20_error)) + throw hidpp20::Error(hidpp20_error.error_code); + + return response; +} + +std::string Device::name() const +{ + return _name; +} + +uint16_t Device::pid() const +{ + return _pid; +} + +void Device::listen() +{ + if(!_raw_device->isListening()) + _raw_device->listenAsync(); + + // Pass all HID++ events with device index to this device. + auto handler = std::make_shared(); + handler->condition = [index=this->_index](std::vector& report) + ->bool { + return (report[Offset::Type] == Report::Type::Short || + report[Offset::Type] == Report::Type::Long) && + (report[Offset::DeviceIndex] == index); + }; + handler->callback = [this](std::vector& report)->void { + Report _report(report); + this->handleEvent(_report); + }; + + _raw_device->addEventHandler("DEV_" + std::to_string(_index), handler); + _listening = true; +} + +void Device::stopListening() +{ + if(_listening) + _raw_device->removeEventHandler("DEV_" + std::to_string(_index)); + + _listening = false; + + if(!_raw_device->eventHandlers().empty()) + _raw_device->stopListener(); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp/Device.h b/src/logid/backend/hidpp/Device.h new file mode 100644 index 0000000..0ea2062 --- /dev/null +++ b/src/logid/backend/hidpp/Device.h @@ -0,0 +1,109 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP_DEVICE_H +#define LOGID_BACKEND_HIDPP_DEVICE_H + +#include +#include +#include +#include +#include "../raw/RawDevice.h" +#include "Report.h" +#include "defs.h" + +namespace logid { +namespace backend { +namespace dj +{ + // Need to define here for a constructor + class Receiver; +} +namespace hidpp +{ + struct DeviceConnectionEvent; + struct EventHandler + { + std::function condition; + std::function callback; + }; + class Device + { + public: + class InvalidDevice : std::exception + { + public: + enum Reason + { + NoHIDPPReport, + InvalidRawDevice, + Asleep + }; + InvalidDevice(Reason reason) : _reason (reason) {} + virtual const char* what() const noexcept; + virtual Reason code() const noexcept; + private: + Reason _reason; + }; + + explicit Device(const std::string& path, DeviceIndex index); + explicit Device(std::shared_ptr raw_device, + DeviceIndex index); + explicit Device(std::shared_ptr receiver, + hidpp::DeviceConnectionEvent event); + ~Device(); + + std::string devicePath() const; + DeviceIndex deviceIndex() const; + std::tuple version() const; + + std::string name() const; + uint16_t pid() const; + + void listen(); // Runs asynchronously + void stopListening(); + + void addEventHandler(const std::string& nickname, + const std::shared_ptr& handler); + void removeEventHandler(const std::string& nickname); + const std::map>& + eventHandlers(); + + Report sendReport(Report& report); + + void handleEvent(Report& report); + private: + void _init(); + + std::shared_ptr _raw_device; + std::shared_ptr _receiver; + std::string _path; + DeviceIndex _index; + uint8_t _supported_reports; + + std::tuple _version; + uint16_t _pid; + std::string _name; + + std::atomic _listening; + + std::map> _event_handlers; + }; +} } } + +#endif //LOGID_BACKEND_HIDPP_DEVICE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp/Report.cpp b/src/logid/backend/hidpp/Report.cpp new file mode 100644 index 0000000..c5683a7 --- /dev/null +++ b/src/logid/backend/hidpp/Report.cpp @@ -0,0 +1,322 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include "Report.h" +#include "../hidpp10/Error.h" +#include "../hidpp20/Error.h" + +using namespace logid::backend::hidpp; +using namespace logid::backend; + +/* Report descriptors were sourced from cvuchener/hidpp */ +static const std::array ShortReportDesc = { + 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 = { + 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 = { + 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 = { + 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 +}; + +uint8_t hidpp::getSupportedReports(std::vector&& rdesc) +{ + uint8_t ret = 0; + + auto it = std::search(rdesc.begin(), rdesc.end(), + ShortReportDesc.begin(), ShortReportDesc.end()); + if(it == rdesc.end()) + it = std::search(rdesc.begin(), rdesc.end(), + ShortReportDesc2.begin(), ShortReportDesc2.end()); + if(it != rdesc.end()) + ret |= HIDPP_REPORT_SHORT_SUPPORTED; + + it = std::search(rdesc.begin(), rdesc.end(), + LongReportDesc.begin(), LongReportDesc.end()); + if(it == rdesc.end()) + it = std::search(rdesc.begin(), rdesc.end(), + LongReportDesc2.begin(), LongReportDesc2.end()); + if(it != rdesc.end()) + ret |= HIDPP_REPORT_LONG_SUPPORTED; + + return ret; +} + +const char *Report::InvalidReportID::what() const noexcept +{ + return "Invalid report ID"; +} + +const char *Report::InvalidReportLength::what() const noexcept +{ + return "Invalid report length"; +} + +Report::Report(Report::Type type, DeviceIndex device_index, + uint8_t sub_id, uint8_t address) +{ + switch(type) { + case Type::Short: + _data.resize(HeaderLength + ShortParamLength); + break; + case Type::Long: + _data.resize(HeaderLength + LongParamLength); + break; + default: + throw InvalidReportID(); + } + + _data[Offset::Type] = type; + _data[Offset::DeviceIndex] = device_index; + _data[Offset::SubID] = sub_id; + _data[Offset::Address] = address; +} + +Report::Report(Report::Type type, DeviceIndex device_index, + uint8_t feature_index, uint8_t function, uint8_t sw_id) +{ + assert(function <= 0x0f); + assert(sw_id <= 0x0f); + + switch(type) { + case Type::Short: + _data.resize(HeaderLength + ShortParamLength); + break; + case Type::Long: + _data.resize(HeaderLength + LongParamLength); + break; + default: + throw InvalidReportID(); + } + + _data[Offset::Type] = type; + _data[Offset::DeviceIndex] = device_index; + _data[Offset::Feature] = feature_index; + _data[Offset::Function] = (function & 0x0f) << 4 | + (sw_id & 0x0f); +} + +Report::Report(const std::vector& data) : + _data (data) +{ + _data.resize(HeaderLength + LongParamLength); + + // Truncating data is entirely valid here. + switch(_data[Offset::Type]) { + case Type::Short: + _data.resize(HeaderLength + ShortParamLength); + break; + case Type::Long: + _data.resize(HeaderLength + LongParamLength); + break; + default: + throw InvalidReportID(); + } +} + +Report::Type Report::type() const +{ + return static_cast(_data[Offset::Type]); +} + +void Report::setType(Report::Type type) +{ + switch(type) { + case Type::Short: + _data.resize(HeaderLength + ShortParamLength); + break; + case Type::Long: + _data.resize(HeaderLength + LongParamLength); + break; + default: + throw InvalidReportID(); + } + + _data[Offset::Type] = type; +} + +hidpp::DeviceIndex Report::deviceIndex() const +{ + return static_cast(_data[Offset::DeviceIndex]); +} + +void Report::setDeviceIndex(hidpp::DeviceIndex index) +{ + _data[Offset::DeviceIndex] = index; +} + +uint8_t Report::feature() const +{ + return _data[Offset::Feature]; +} + +void Report::setFeature(uint8_t feature) +{ + _data[Offset::Parameters] = feature; +} + +uint8_t Report::subId() const +{ + return _data[Offset::SubID]; +} + +void Report::setSubId(uint8_t sub_id) +{ + _data[Offset::SubID] = sub_id; +} + +uint8_t Report::function() const +{ + return (_data[Offset::Function] >> 4) & 0x0f; +} + +void Report::setFunction(uint8_t function) +{ + _data[Offset::Function] &= 0x0f; + _data[Offset::Function] |= (function & 0x0f) << 4; +} + +uint8_t Report::swId() const +{ + return _data[Offset::Function] & 0x0f; +} + +void Report::setSwId(uint8_t sub_id) +{ + _data[Offset::Function] &= 0x0f; + _data[Offset::Function] |= sub_id & 0x0f; +} + +uint8_t Report::address() const +{ + return _data[Offset::Address]; +} + +void Report::setAddress(uint8_t address) +{ + _data[Offset::Address] = address; +} + +std::vector::iterator Report::paramBegin() +{ + return _data.begin() + Offset::Parameters; +} + +std::vector::iterator Report::paramEnd() +{ + return _data.end(); +} + +std::vector::const_iterator Report::paramBegin() const +{ + return _data.begin() + Offset::Parameters; +} + +std::vector::const_iterator Report::paramEnd() const +{ + return _data.end(); +} + +void Report::setParams(const std::vector& _params) +{ + assert(_params.size() <= _data.size()-HeaderLength); + + for(std::size_t i = 0; i < _params.size(); i++) + _data[Offset::Parameters + i] = _params[i]; +} + +bool Report::isError10(Report::Hidpp10Error *error) +{ + assert(error != nullptr); + + if(_data[Offset::Type] != Type::Short || + _data[Offset::SubID] != hidpp10::ErrorID) + return false; + + error->sub_id = _data[3]; + error->address = _data[4]; + error->error_code = _data[5]; + + return true; +} + +bool Report::isError20(Report::Hidpp20Error* error) +{ + assert(error != nullptr); + + if(_data[Offset::Type] != Type::Long || + _data[Offset::Feature] != hidpp20::ErrorID) + return false; + + error->feature_index= _data[3]; + error->function = (_data[4] >> 4) & 0x0f; + error->software_id = _data[4] & 0x0f; + error->error_code = _data[5]; + + return true; +} \ No newline at end of file diff --git a/src/logid/backend/hidpp/Report.h b/src/logid/backend/hidpp/Report.h new file mode 100644 index 0000000..e406a6d --- /dev/null +++ b/src/logid/backend/hidpp/Report.h @@ -0,0 +1,125 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP_REPORT_H +#define LOGID_BACKEND_HIDPP_REPORT_H + +#include +#include "../raw/RawDevice.h" +#include "defs.h" + +/* Some devices only support a subset of these reports */ +#define HIDPP_REPORT_SHORT_SUPPORTED 1U +#define HIDPP_REPORT_LONG_SUPPORTED 1U<<1U +/* Very long reports exist, however they have not been encountered so far */ + +namespace logid { +namespace backend { +namespace hidpp +{ + uint8_t getSupportedReports(std::vector&& rdesc); + + namespace Offset + { + static constexpr uint8_t Type = 0; + static constexpr uint8_t DeviceIndex = 1; + static constexpr uint8_t SubID = 2; + static constexpr uint8_t Feature = 2; + static constexpr uint8_t Address = 3; + static constexpr uint8_t Function = 3; + static constexpr uint8_t Parameters = 4; + } + + class Report + { + public: + typedef ReportType::ReportType Type; + + class InvalidReportID: public std::exception + { + public: + InvalidReportID() = default; + const char* what() const noexcept override; + }; + + class InvalidReportLength: public std::exception + { + public: + InvalidReportLength() = default;; + const char* what() const noexcept override; + }; + + static constexpr std::size_t MaxDataLength = 20; + + Report(Report::Type type, DeviceIndex device_index, + uint8_t sub_id, + uint8_t address); + Report(Report::Type type, DeviceIndex device_index, + uint8_t feature_index, + uint8_t function, + uint8_t sw_id); + explicit Report(const std::vector& data); + + Report::Type type() const; + void setType(Report::Type type); + + logid::backend::hidpp::DeviceIndex deviceIndex() const; + void setDeviceIndex(hidpp::DeviceIndex index); + + uint8_t feature() const; + void setFeature(uint8_t feature); + + uint8_t subId() const; + void setSubId(uint8_t sub_id); + + uint8_t function() const; + void setFunction(uint8_t function); + + uint8_t swId() const; + void setSwId(uint8_t sw_id); + + uint8_t address() const; + void setAddress(uint8_t address); + + std::vector::iterator paramBegin(); + std::vector::iterator paramEnd(); + std::vector::const_iterator paramBegin() const; + std::vector::const_iterator paramEnd() const; + void setParams(const std::vector& _params); + + struct Hidpp10Error + { + uint8_t sub_id, address, error_code; + }; + bool isError10(Hidpp10Error* error); + + struct Hidpp20Error + { + uint8_t feature_index, function, software_id, error_code; + }; + bool isError20(Hidpp20Error* error); + + std::vector rawReport () const { return _data; } + + static constexpr std::size_t HeaderLength = 4; + private: + std::vector _data; + }; +}}} + +#endif //LOGID_BACKEND_HIDPP_REPORT_H \ No newline at end of file diff --git a/src/logid/backend/hidpp/defs.h b/src/logid/backend/hidpp/defs.h new file mode 100644 index 0000000..3a85470 --- /dev/null +++ b/src/logid/backend/hidpp/defs.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP_DEFS_H +#define LOGID_BACKEND_HIDPP_DEFS_H + +#define LOGID_HIDPP_SOFTWARE_ID 0 + +#include + +namespace logid { +namespace backend { +namespace hidpp +{ + namespace ReportType + { + enum ReportType : uint8_t + { + Short = 0x10, + Long = 0x11 + }; + } + + enum DeviceIndex: uint8_t + { + DefaultDevice = 0xff, + CordedDevice = 0, + WirelessDevice1 = 1, + WirelessDevice2 = 2, + WirelessDevice3 = 3, + WirelessDevice4 = 4, + WirelessDevice5 = 5, + WirelessDevice6 = 6, + }; + + static constexpr std::size_t ShortParamLength = 3; + static constexpr std::size_t LongParamLength = 16; +} } } + +#endif //LOGID_BACKEND_HIDPP_DEFS_H \ No newline at end of file diff --git a/src/logid/backend/hidpp10/Device.cpp b/src/logid/backend/hidpp10/Device.cpp new file mode 100644 index 0000000..d35d0d6 --- /dev/null +++ b/src/logid/backend/hidpp10/Device.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include "Device.h" +#include "defs.h" + +using namespace logid::backend; +using namespace logid::backend::hidpp10; + +Device::Device(const std::string &path, hidpp::DeviceIndex index) : + hidpp::Device(path, index) +{ + assert(version() == std::make_tuple(1, 0)); +} + +Device::Device(std::shared_ptr raw_dev, + hidpp::DeviceIndex index) : hidpp::Device(std::move(raw_dev), index) +{ + assert(version() == std::make_tuple(1, 0)); +} + +std::vector Device::getRegister(uint8_t address, + const std::vector& params, hidpp::Report::Type type) +{ + assert(params.size() <= hidpp::LongParamLength); + + uint8_t sub_id = type == hidpp::Report::Type::Short ? + GetRegisterShort : GetRegisterLong; + + return accessRegister(sub_id, address, params); +} + +std::vector Device::setRegister(uint8_t address, + const std::vector& params, + hidpp::Report::Type type) +{ + assert(params.size() <= hidpp::LongParamLength); + + uint8_t sub_id = type == hidpp::Report::Type::Short ? + SetRegisterShort : SetRegisterLong; + + return accessRegister(sub_id, address, params); +} + +std::vector Device::accessRegister(uint8_t sub_id, uint8_t address, + const std::vector ¶ms) +{ + hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ? + hidpp::Report::Type::Short : hidpp::Report::Type::Long; + + hidpp::Report request(type, deviceIndex(), sub_id, address); + std::copy(params.begin(), params.end(), request.paramBegin()); + + auto response = sendReport(request); + return std::vector(response.paramBegin(), response.paramEnd()); +} + + + diff --git a/src/logid/backend/hidpp10/Device.h b/src/logid/backend/hidpp10/Device.h new file mode 100644 index 0000000..b7ff479 --- /dev/null +++ b/src/logid/backend/hidpp10/Device.h @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP10_DEVICE_H +#define LOGID_BACKEND_HIDPP10_DEVICE_H + +#include "../hidpp/Device.h" + +namespace logid { +namespace backend { +namespace hidpp10 +{ + class Device : public hidpp::Device + { + public: + Device(const std::string& path, hidpp::DeviceIndex index); + Device(std::shared_ptr raw_dev, + hidpp::DeviceIndex index); + + std::vector getRegister(uint8_t address, + const std::vector& params, hidpp::Report::Type type); + + std::vector setRegister(uint8_t address, + const std::vector& params, hidpp::Report::Type type); + private: + std::vector accessRegister(uint8_t sub_id, + uint8_t address, const std::vector& params); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP10_DEVICE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp10/Error.cpp b/src/logid/backend/hidpp10/Error.cpp new file mode 100644 index 0000000..76c8415 --- /dev/null +++ b/src/logid/backend/hidpp10/Error.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include "Error.h" + +using namespace logid::backend::hidpp10; + +Error::Error(uint8_t code): _code(code) +{ + assert(code != Success); +} + +const char* Error::what() const noexcept +{ + switch(_code) { + case Success: + return "Success"; + case InvalidSubID: + return "Invalid sub ID"; + case InvalidAddress: + return "Invalid address"; + case InvalidValue: + return "Invalid value"; + case ConnectFail: + return "Connection failure"; + 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 InvalidParameterValue: + return "Invalid parameter value"; + case WrongPINCode: + return "Wrong PIN code"; + default: + return "Unknown error code"; + } +} + +uint8_t Error::code() const noexcept +{ + return _code; +} \ No newline at end of file diff --git a/src/logid/backend/hidpp10/Error.h b/src/logid/backend/hidpp10/Error.h new file mode 100644 index 0000000..f5fffae --- /dev/null +++ b/src/logid/backend/hidpp10/Error.h @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP10_ERROR_H +#define LOGID_BACKEND_HIDPP10_ERROR_H + +#include + +namespace logid { +namespace backend { +namespace hidpp10 { + static constexpr uint8_t ErrorID = 0x8f; + + 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, + InvalidParameterValue = 0x0B, + WrongPINCode = 0x0C + }; + + explicit Error(uint8_t code); + + const char* what() const noexcept override; + uint8_t code() const noexcept; + + private: + uint8_t _code; + }; +}}} + +#endif //LOGID_BACKEND_HIDPP10_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/hidpp10/defs.h b/src/logid/backend/hidpp10/defs.h new file mode 100644 index 0000000..d3c06b8 --- /dev/null +++ b/src/logid/backend/hidpp10/defs.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP10_DEFS_H +#define LOGID_BACKEND_HIDPP10_DEFS_H + +namespace logid { +namespace backend { +namespace hidpp10 +{ + enum SubID: uint8_t + { + SetRegisterShort = 0x80, + GetRegisterShort = 0x81, + SetRegisterLong = 0x82, + GetRegisterLong = 0x83 + }; +}}} + +#endif //LOGID_BACKEND_HIDPP10_DEFS_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Device.cpp b/src/logid/backend/hidpp20/Device.cpp new file mode 100644 index 0000000..691cc34 --- /dev/null +++ b/src/logid/backend/hidpp20/Device.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include + +#include "Device.h" +#include "../hidpp/defs.h" + +using namespace logid::backend::hidpp20; + +Device::Device(std::string path, hidpp::DeviceIndex index) + : hidpp::Device(path, index) +{ + assert(std::get<0>(version()) >= 2); +} + +Device::Device(std::shared_ptr raw_device, hidpp::DeviceIndex index) + : hidpp::Device(raw_device, index) +{ + assert(std::get<0>(version()) >= 2); +} + +std::vector Device::callFunction(uint8_t feature_index, + uint8_t function, std::vector& params) +{ + hidpp::Report::Type type; + + assert(params.size() <= hidpp::LongParamLength); + if(params.size() <= hidpp::ShortParamLength) + type = hidpp::Report::Type::Short; + else if(params.size() <= hidpp::LongParamLength) + type = hidpp::Report::Type::Long; + else + throw hidpp::Report::InvalidReportID(); + + hidpp::Report request(type, deviceIndex(), feature_index, function, + LOGID_HIDPP_SOFTWARE_ID); + std::copy(params.begin(), params.end(), request.paramBegin()); + + auto response = this->sendReport(request); + return std::vector(response.paramBegin(), response.paramEnd()); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Device.h b/src/logid/backend/hidpp20/Device.h new file mode 100644 index 0000000..747d36d --- /dev/null +++ b/src/logid/backend/hidpp20/Device.h @@ -0,0 +1,40 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP20_DEVICE_H +#define LOGID_BACKEND_HIDPP20_DEVICE_H + +#include "../hidpp/Device.h" +#include + +namespace logid { +namespace backend { +namespace hidpp20 { + class Device : public hidpp::Device + { + public: + Device(std::string path, hidpp::DeviceIndex index); + Device(std::shared_ptr raw_device, hidpp::DeviceIndex index); + + std::vector callFunction(uint8_t feature_index, + uint8_t function, + std::vector& params); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_DEVICE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Error.cpp b/src/logid/backend/hidpp20/Error.cpp new file mode 100644 index 0000000..eea11a9 --- /dev/null +++ b/src/logid/backend/hidpp20/Error.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "Error.h" + +using namespace logid::backend::hidpp20; + +Error::Error(uint8_t code) : _code (code) +{ + assert(_code != NoError); +} + +const char* Error::what() const noexcept +{ + switch(_code) { + case NoError: + return "No error"; + case Unknown: + return "Unknown"; + case InvalidArgument: + return "Invalid argument"; + case OutOfRange: + return "Out of range"; + case HardwareError: + return "Hardware error"; + case LogitechInternal: + return "Logitech internal feature"; + case InvalidFeatureIndex: + return "Invalid feature index"; + case InvalidFunctionID: + return "Invalid function ID"; + case Busy: + return "Busy"; + case Unsupported: + return "Unsupported"; + case UnknownDevice: + return "Unknown device"; + default: + return "Unknown error code"; + } +} + +uint8_t Error::code() const noexcept +{ + return _code; +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Error.h b/src/logid/backend/hidpp20/Error.h new file mode 100644 index 0000000..06b5132 --- /dev/null +++ b/src/logid/backend/hidpp20/Error.h @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP20_ERROR_H +#define LOGID_BACKEND_HIDPP20_ERROR_H + +#include +#include + +namespace logid { +namespace backend { +namespace hidpp20 { + static constexpr uint8_t ErrorID = 0xFF; + + class Error: public std::exception + { + public: + enum ErrorCode: uint8_t { + NoError = 0, + Unknown = 1, + InvalidArgument = 2, + OutOfRange = 3, + HardwareError = 4, + LogitechInternal = 5, + InvalidFeatureIndex = 6, + InvalidFunctionID = 7, + Busy = 8, + Unsupported = 9, + UnknownDevice = 10 + }; + + explicit Error(uint8_t code); + + const char* what() const noexcept override; + uint8_t code() const noexcept; + + private: + uint8_t _code; + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_ERROR_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/EssentialFeature.cpp b/src/logid/backend/hidpp20/EssentialFeature.cpp new file mode 100644 index 0000000..11b9751 --- /dev/null +++ b/src/logid/backend/hidpp20/EssentialFeature.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "EssentialFeature.h" +#include "feature_defs.h" +#include "features/Root.h" +#include "Error.h" + +using namespace logid::backend::hidpp20; + +std::vector EssentialFeature::callFunction(uint8_t function_id, + std::vector& params) +{ + hidpp::Report::Type type; + + assert(params.size() <= hidpp::LongParamLength); + if(params.size() <= hidpp::ShortParamLength) + type = hidpp::Report::Type::Short; + else if(params.size() <= hidpp::LongParamLength) + type = hidpp::Report::Type::Long; + else + throw hidpp::Report::InvalidReportID(); + + hidpp::Report request(type, _device->deviceIndex(), _index, function_id, + LOGID_HIDPP_SOFTWARE_ID); + std::copy(params.begin(), params.end(), request.paramBegin()); + + auto response = _device->sendReport(request); + return std::vector(response.paramBegin(), response.paramEnd()); +} + +EssentialFeature::EssentialFeature(hidpp::Device* dev, uint16_t _id) : + _device (dev) +{ + _index = hidpp20::FeatureID::ROOT; + + if(_id) + { + std::vector getFunc_req(2); + getFunc_req[0] = (_id >> 8) & 0xff; + getFunc_req[1] = _id & 0xff; + try { + auto getFunc_resp = this->callFunction(Root::GetFeature, + getFunc_req); + _index = getFunc_resp[0]; + } catch(Error& e) { + if(e.code() == Error::InvalidFeatureIndex) + throw UnsupportedFeature(_id); + throw e; + } + + // 0 if not found + if(!_index) + throw UnsupportedFeature(_id); + } +} diff --git a/src/logid/backend/hidpp20/EssentialFeature.h b/src/logid/backend/hidpp20/EssentialFeature.h new file mode 100644 index 0000000..bfc578f --- /dev/null +++ b/src/logid/backend/hidpp20/EssentialFeature.h @@ -0,0 +1,50 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H +#define LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H + +// WARNING: UNSAFE + +/* This class is only meant to provide essential HID++ 2.0 features to the + * hidpp::Device class. No version checks are provided here + */ + +#include "Device.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class EssentialFeature + { + public: + static const uint16_t ID; + virtual uint16_t getID() = 0; + + protected: + EssentialFeature(hidpp::Device* dev, uint16_t _id); + std::vector callFunction(uint8_t function_id, + std::vector& params); + private: + hidpp::Device* _device; + uint8_t _index; + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Feature.cpp b/src/logid/backend/hidpp20/Feature.cpp new file mode 100644 index 0000000..18c30b1 --- /dev/null +++ b/src/logid/backend/hidpp20/Feature.cpp @@ -0,0 +1,70 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "Error.h" +#include "Feature.h" +#include "feature_defs.h" +#include "features/Root.h" + +using namespace logid::backend::hidpp20; + +const char* UnsupportedFeature::what() const noexcept +{ + return "Unsupported feature"; +} + +uint16_t UnsupportedFeature::code() const noexcept +{ + return _f_id; +} + +std::vector Feature::callFunction(uint8_t function_id, std::vector& params) +{ + return _device->callFunction(_index, function_id, params); +} + +Feature::Feature(Device* dev, uint16_t _id) : _device (dev) +{ + _index = hidpp20::FeatureID::ROOT; + + if(_id) + { + std::vector getFunc_req(2); + getFunc_req[0] = (_id >> 8) & 0xff; + getFunc_req[1] = _id & 0xff; + + try { + auto getFunc_resp = this->callFunction(Root::GetFeature, + getFunc_req); + _index = getFunc_resp[0]; + } catch(Error& e) { + if(e.code() == Error::InvalidFeatureIndex) + throw UnsupportedFeature(_id); + throw e; + } + + // 0 if not found + if(!_index) + throw UnsupportedFeature(_id); + } +} + +uint8_t Feature::featureIndex() +{ + return _index; +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/Feature.h b/src/logid/backend/hidpp20/Feature.h new file mode 100644 index 0000000..06ec38f --- /dev/null +++ b/src/logid/backend/hidpp20/Feature.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_H +#define LOGID_BACKEND_HIDPP20_FEATURE_H + +#include +#include "Device.h" + +namespace logid { +namespace backend { +namespace hidpp20 { + class UnsupportedFeature : public std::exception + { + public: + explicit UnsupportedFeature(uint16_t ID) : _f_id (ID) {} + const char* what() const noexcept override; + uint16_t code() const noexcept; + private: + uint16_t _f_id; + }; + + class Feature + { + public: + static const uint16_t ID; + virtual uint16_t getID() = 0; + uint8_t featureIndex(); + protected: + explicit Feature(Device* dev, uint16_t _id); + std::vector callFunction(uint8_t function_id, + std::vector& params); + private: + Device* _device; + uint8_t _index; + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/feature_defs.h b/src/logid/backend/hidpp20/feature_defs.h new file mode 100644 index 0000000..eb0fc9d --- /dev/null +++ b/src/logid/backend/hidpp20/feature_defs.h @@ -0,0 +1,131 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP20_FEATUREDEFS +#define LOGID_BACKEND_HIDPP20_FEATUREDEFS + +#include + +namespace logid { +namespace backend { +namespace hidpp20 { + struct feature_info { + uint16_t feature_id; + bool obsolete; + bool internal; + bool hidden; + }; + + namespace FeatureID + { + enum FeatureID : uint16_t + { + ROOT = 0x0000, + FEATURE_SET = 0x0001, + FEATURE_INFO = 0x0002, + FW_VERSION = 0x0003, + DEVICE_NAME = 0x0005, + DEVICE_GROUPS = 0x0006, + DEVICE_FRIENDLY_NAME = 0x0007, + RESET = 0x0020, + CRYPTO_IDENTIFIER = 0x0021, + DFUCONTROL = 0x00c0, + DFUCONTROL_V2 = 0x00c1, + DFUCONTROL_V3 = 0x00c2, + DFU = 0xd000, + BATTERY_STATUS = 0x1000, + BATTERY_VOLTAGE = 0x1001, + CHARGING_CONTROL = 0x1010, + LED_CONTROL = 0x1300, + GENERIC_TEST = 0x1800, + DEVICE_RESET = 0x1802, + OOB_STATE = 0x1805, + CONFIGURABLE_DEVICE_PROPERTIES = 0x1806, + CHANGE_HOST = 0x1814, + HOSTS_INFO = 0x1815, + BACKLIGHT = 0x1981, + BACKLIGHT_V2 = 0x1982, + BACKLIGHT_V3 = 0x1983, + PRESENTER_CONTROL = 0x1a00, + SENSOR_3D = 0x1a01, + REPROG_CONTROLS = 0x1b00, + REPROG_CONTROLS_V2 = 0x1b01, + REPROG_CONTROLS_V2_2 = 0x1b02, + REPROG_CONTROLS_V3 = 0x1b03, + REPROG_CONTROLS_V4 = 0x1b04, + PERSISTENT_REMAPPABLE_ACTION = 0x1bc0, + WIRELESS_DEVICE_STATUS = 0x1d4b, + ENABLE_HIDDEN_FEATURE = 0x1e00, + FIRMWARE_PROPERTIES = 0x1f1f, + ADC_MEASUREMENT = 0x1f20, + LEFT_RIGHT_SWAP = 0x2001, + SWAP_BUTTON = 0x2005, + POINTER_AXES_ORIENTATION = 0x2006, + VERTICAL_SCROLLING = 0x2100, + SMART_SHIFT = 0x2110, + HIRES_SCROLLING = 0x2120, + HIRES_SCROLLING_V2 = 0x2121, // Referred to as Hi-res wheel in cvuchener/hidpp, seems to be V2? + LORES_SCROLLING = 0x2130, + MOUSE_POINTER = 0x2200, // Possibly predecessor to 0x2201? + ADJUSTABLE_DPI = 0x2201, + ANGLE_SNAPPING = 0x2230, + SURFACE_TUNING = 0x2240, + HYBRID_TRACKING = 0x2400, + FN_INVERSION = 0x40a0, + FN_INVERSION_V2 = 0x40a2, // Is 0x40a1 skipped? + FN_INVERSION_V3 = 0x40a3, + ENCRYPTION = 0x4100, + LOCK_KEY_STATE = 0x4220, + SOLAR_DASHBOARD = 0x4301, + KEYBOARD_LAYOUT = 0x4520, + KEYBOARD_DISABLE = 0x4521, + DISABLE_KEYS = 0x4522, + MULTIPLATFORM = 0x4530, // Dual platform only? + MULTIPLATFORM_V2 = 0x4531, + KEYBOARD_LAYOUT_V2 = 0x4540, + CROWN = 0x4600, + TOUCHPAD_FW = 0x6010, + TOUCHPAD_SW = 0x6011, + TOUCHPAD_FW_WIN8 = 0x6012, + TOUCHMOUSE_RAW = 0x6100, + // TOUCHMOUSE_6120 = 0x6120, (Keeping this commented out until a better name is found) + GESTURE = 0x6500, + GESTURE_V2 = 0x6501, + G_KEY = 0x8010, + M_KEY = 0x8020, + // MR = 0x8030, (Keeping this commented out until a better name is found) + BRIGHTNESS_CONTROL = 0x8040, + REPORT_RATE = 0x8060, + RGB_EFFECTS = 0x8070, + RGB_EFFECTS_V2 = 0x8071, + PER_KEY_LIGHTING = 0x8080, + PER_KEY_LIGHTING_V2 = 0x8081, + MODE_STATUS = 0x8100, + MOUSE_BUTTON_SPY = 0x8110, + LATENCY_MONITORING = 0x8111, + GAMING_ATTACHMENTS = 0x8120, + FORCE_FEEDBACK = 0x8123, + SIDETONE = 0x8300, + EQUALIZER = 0x8310, + HEADSET_OUT = 0x8320 + }; + } + +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/AdjustableDPI.cpp b/src/logid/backend/hidpp20/features/AdjustableDPI.cpp new file mode 100644 index 0000000..722d6cb --- /dev/null +++ b/src/logid/backend/hidpp20/features/AdjustableDPI.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "AdjustableDPI.h" + +using namespace logid::backend::hidpp20; + +AdjustableDPI::AdjustableDPI(Device* dev) : Feature(dev, ID) +{ +} + +uint8_t AdjustableDPI::getSensorCount() +{ + std::vector params(0); + auto response = callFunction(GetSensorCount, params); + return response[0]; +} + +AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor) +{ + SensorDPIList dpi_list{}; + std::vector params(1); + params[0] = sensor; + auto response = callFunction(GetSensorDPIList, params); + + dpi_list.dpiStep = false; + for(std::size_t i = 1; i < response.size(); i+=2) { + uint16_t dpi = response[i + 1]; + dpi |= (response[i] << 8); + if(!dpi) + break; + if(dpi >= 0xe000) { + dpi_list.isRange = true; + dpi_list.dpiStep = dpi - 0xe000; + } else { + dpi_list.dpis.push_back(dpi); + } + } + + return dpi_list; +} + +uint16_t AdjustableDPI::getDefaultSensorDPI(uint8_t sensor) +{ + std::vector params(1); + params[0] = sensor; + auto response = callFunction(GetSensorDPI, params); + + uint16_t default_dpi = response[4]; + default_dpi |= (response[3] << 8); + + return default_dpi; +} + +uint16_t AdjustableDPI::getSensorDPI(uint8_t sensor) +{ + std::vector params(1); + params[0] = sensor; + auto response = callFunction(GetSensorDPI, params); + + uint16_t dpi = response[2]; + dpi |= (response[1] << 8); + + return dpi; +} + +void AdjustableDPI::setSensorDPI(uint8_t sensor, uint16_t dpi) +{ + std::vector params(3); + params[0] = sensor; + params[1] = (dpi >> 8); + params[2] = (dpi & 0xFF); + callFunction(SetSensorDPI, params); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/AdjustableDPI.h b/src/logid/backend/hidpp20/features/AdjustableDPI.h new file mode 100644 index 0000000..9a76d0f --- /dev/null +++ b/src/logid/backend/hidpp20/features/AdjustableDPI.h @@ -0,0 +1,60 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H +#define LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H + +#include "../feature_defs.h" +#include "../Feature.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class AdjustableDPI : public Feature + { + public: + static const uint16_t ID = FeatureID::ADJUSTABLE_DPI; + virtual uint16_t getID() { return ID; } + + enum Function { + GetSensorCount = 0, + GetSensorDPIList = 1, + GetSensorDPI = 2, + SetSensorDPI = 3 + }; + + AdjustableDPI(Device* dev); + + uint8_t getSensorCount(); + + struct SensorDPIList + { + std::vector dpis; + bool isRange; + uint16_t dpiStep; + }; + SensorDPIList getSensorDPIList(uint8_t sensor); + + uint16_t getDefaultSensorDPI(uint8_t sensor); + uint16_t getSensorDPI(uint8_t sensor); + + void setSensorDPI(uint8_t sensor, uint16_t dpi); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_ADJUSTABLEDPI_H diff --git a/src/logid/backend/hidpp20/features/DeviceName.cpp b/src/logid/backend/hidpp20/features/DeviceName.cpp new file mode 100644 index 0000000..d6c44bc --- /dev/null +++ b/src/logid/backend/hidpp20/features/DeviceName.cpp @@ -0,0 +1,86 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "DeviceName.h" + +using namespace logid::backend; +using namespace logid::backend::hidpp20; + +DeviceName::DeviceName(Device* dev) : Feature(dev, ID) +{ +} + +uint8_t DeviceName::getNameLength() +{ + std::vector params(0); + + auto response = this->callFunction(Function::GetLength, params); + return response[0]; +} + +std::string _getName(uint8_t length, + const std::function(std::vector)>& fcall) +{ + uint8_t function_calls = length/hidpp::LongParamLength; + if(length % hidpp::LongParamLength) + function_calls++; + std::vector params(1); + std::string name; + + for(uint8_t i = 0; i < function_calls; i++) { + params[0] = i*hidpp::LongParamLength; + auto name_section = fcall(params); + for(std::size_t j = 0; j < hidpp::LongParamLength; j++) { + if(params[0] + j >= length) + return name; + name += name_section[j]; + } + } + + return name; +} + +std::string DeviceName::getName() +{ + return _getName(getNameLength(), [this] + (std::vector params)->std::vector { + return this->callFunction(Function::GetDeviceName, params); + }); +} + +EssentialDeviceName::EssentialDeviceName(hidpp::Device* dev) : + EssentialFeature(dev, ID) +{ +} + +uint8_t EssentialDeviceName::getNameLength() +{ + std::vector params(0); + + auto response = this->callFunction(DeviceName::Function::GetLength, params); + return response[0]; +} + +std::string EssentialDeviceName::getName() +{ + return _getName(getNameLength(), [this] + (std::vector params)->std::vector { + return this->callFunction(DeviceName::Function::GetDeviceName, params); + }); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/DeviceName.h b/src/logid/backend/hidpp20/features/DeviceName.h new file mode 100644 index 0000000..e21fe27 --- /dev/null +++ b/src/logid/backend/hidpp20/features/DeviceName.h @@ -0,0 +1,61 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H +#define LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H + +#include "../Feature.h" +#include "../feature_defs.h" +#include "../EssentialFeature.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class DeviceName : public Feature + { + public: + static const uint16_t ID = FeatureID::DEVICE_NAME; + virtual uint16_t getID() { return ID; } + + enum Function : uint8_t + { + GetLength = 0, + GetDeviceName = 1 + }; + + explicit DeviceName(Device* device); + + uint8_t getNameLength(); + std::string getName(); + }; + + class EssentialDeviceName : public EssentialFeature + { + public: + static const uint16_t ID = FeatureID::DEVICE_NAME; + virtual uint16_t getID() { return ID; } + + explicit EssentialDeviceName(hidpp::Device* device); + + uint8_t getNameLength(); + std::string getName(); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_DEVICENAME_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/FeatureSet.cpp b/src/logid/backend/hidpp20/features/FeatureSet.cpp new file mode 100644 index 0000000..092b809 --- /dev/null +++ b/src/logid/backend/hidpp20/features/FeatureSet.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "FeatureSet.h" + +using namespace logid::backend::hidpp20; + +FeatureSet::FeatureSet(Device *device) : Feature(device, ID) +{ +} + +uint8_t FeatureSet::getFeatureCount() +{ + std::vector params(0); + auto response = callFunction(GetFeatureCount, params); + return response[0]; +} + +uint16_t FeatureSet::getFeature(uint8_t feature_index) +{ + std::vector params(1); + params[0] = feature_index; + auto response = callFunction(GetFeature, params); + + uint16_t feature_id = (response[0] << 8); + feature_id |= response[1]; + return feature_id; +} + +std::map FeatureSet::getFeatures() +{ + uint8_t feature_count = getFeatureCount(); + std::map features; + for(uint8_t i = 0; i < feature_count; i++) + features[i] = getFeature(i); + return features; +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/FeatureSet.h b/src/logid/backend/hidpp20/features/FeatureSet.h new file mode 100644 index 0000000..27a814e --- /dev/null +++ b/src/logid/backend/hidpp20/features/FeatureSet.h @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H +#define LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H + +#include "../Feature.h" +#include "../feature_defs.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class FeatureSet : public Feature + { + public: + static const uint16_t ID = FeatureID::FEATURE_SET; + virtual uint16_t getID() { return ID; } + + enum Function : uint8_t + { + GetFeatureCount = 0, + GetFeature = 1 + }; + + explicit FeatureSet(Device* device); + + uint8_t getFeatureCount(); + uint16_t getFeature(uint8_t feature_index); + std::map getFeatures(); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H diff --git a/src/logid/backend/hidpp20/features/HiresScroll.cpp b/src/logid/backend/hidpp20/features/HiresScroll.cpp new file mode 100644 index 0000000..62bc597 --- /dev/null +++ b/src/logid/backend/hidpp20/features/HiresScroll.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "HiresScroll.h" + +using namespace logid::backend::hidpp20; + +HiresScroll::HiresScroll(Device *device) : Feature(device, ID) +{ +} + +HiresScroll::Capabilities HiresScroll::getCapabilities() +{ + std::vector params(0); + auto response = callFunction(GetCapabilities, params); + + Capabilities capabilities{}; + capabilities.multiplier = response[0]; + capabilities.flags = response[1]; + return capabilities; +} + +uint8_t HiresScroll::getMode() +{ + std::vector params(0); + auto response = callFunction(GetMode, params); + return response[0]; +} + +void HiresScroll::setMode(uint8_t mode) +{ + std::vector params(1); + params[0] = mode; + callFunction(SetMode, params); +} + +bool HiresScroll::getRatchetState() +{ + std::vector params(0); + auto response = callFunction(GetRatchetState, params); + return params[0]; +} + +HiresScroll::WheelStatus HiresScroll::wheelMovementEvent(const hidpp::Report + &report) +{ + assert(report.function() == WheelMovement); + WheelStatus status{}; + status.hiRes = report.paramBegin()[0] & 1<<4; + status.periods = report.paramBegin()[0] & 0x0F; + status.deltaV = report.paramBegin()[1] << 8 | report.paramBegin()[2]; + return status; +} + +HiresScroll::RatchetState HiresScroll::ratchetSwitchEvent(const hidpp::Report + &report) +{ + assert(report.function() == WheelMovement); + // Possible bad cast + return static_cast(report.paramBegin()[0]); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/HiresScroll.h b/src/logid/backend/hidpp20/features/HiresScroll.h new file mode 100644 index 0000000..69dee11 --- /dev/null +++ b/src/logid/backend/hidpp20/features/HiresScroll.h @@ -0,0 +1,93 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H +#define LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H + +#include "../Feature.h" +#include "../feature_defs.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class HiresScroll : public Feature + { + public: + ///TODO: Hires scroll V1? + static const uint16_t ID = FeatureID::HIRES_SCROLLING_V2; + virtual uint16_t getID() { return ID; } + + enum Function : uint8_t + { + GetCapabilities = 0, + GetMode = 1, + SetMode = 2, + GetRatchetState = 3 + }; + + enum Event : uint8_t + { + WheelMovement = 0, + RatchetSwitch = 1, + }; + + enum Capability : uint8_t + { + Invertable = 1<<3, + HasRatchet = 1<<2 + }; + + enum Mode : uint8_t + { + Inverted = 1<<2, + HiRes = 1<<1, + Target = 1 + }; + + enum RatchetState : uint8_t + { + FreeWheel = 0, + Ratchet = 1 + }; + + struct Capabilities + { + uint8_t multiplier; + uint8_t flags; + }; + + struct WheelStatus + { + bool hiRes; + uint8_t periods; + uint16_t deltaV; + }; + + explicit HiresScroll(Device* device); + + Capabilities getCapabilities(); + uint8_t getMode(); + void setMode(uint8_t mode); + bool getRatchetState(); + + static WheelStatus wheelMovementEvent(const hidpp::Report& report); + static RatchetState ratchetSwitchEvent(const hidpp::Report& report); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_HIRESSCROLL_H diff --git a/src/logid/backend/hidpp20/features/ReprogControls.cpp b/src/logid/backend/hidpp20/features/ReprogControls.cpp new file mode 100644 index 0000000..ce10bb0 --- /dev/null +++ b/src/logid/backend/hidpp20/features/ReprogControls.cpp @@ -0,0 +1,189 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "../Error.h" +#include "ReprogControls.h" + +using namespace logid::backend::hidpp20; + +#define DEFINE_REPROG(x, base) \ +x::x(Device* dev) : base(dev, ID) \ +{ \ +} \ +x::x(Device* dev, uint16_t _id) : base(dev, _id) \ +{ \ +} + +#define MAKE_REPROG(x, dev) \ +try { \ + return std::make_shared(dev); \ +} catch(UnsupportedFeature &e) {\ +} + +// Define all of the ReprogControls versions +DEFINE_REPROG(ReprogControls, Feature) +DEFINE_REPROG(ReprogControlsV2, ReprogControls) +DEFINE_REPROG(ReprogControlsV2_2, ReprogControlsV2) +DEFINE_REPROG(ReprogControlsV3, ReprogControlsV2_2) +DEFINE_REPROG(ReprogControlsV4, ReprogControlsV3) + +std::shared_ptr ReprogControls::autoVersion(Device *dev) +{ + MAKE_REPROG(ReprogControlsV4, dev) + MAKE_REPROG(ReprogControlsV3, dev) + MAKE_REPROG(ReprogControlsV2_2, dev) + MAKE_REPROG(ReprogControlsV2, dev) + + // If base version cannot be made, throw error + return std::make_shared(dev); +} + +uint8_t ReprogControls::getControlCount() +{ + std::vector params(0); + auto response = callFunction(GetControlCount, params); + return response[0]; +} + +ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index) +{ + std::vector params(1); + ControlInfo info{}; + params[0] = index; + auto response = callFunction(GetControlInfo, params); + + info.controlID = response[1]; + info.controlID |= response[0] << 8; + info.taskID = response[3]; + info.taskID |= response[2] << 8; + info.flags = response[4]; + info.position = response[5]; + info.group = response[6]; + info.groupMask = response[7]; + info.additionalFlags = response[8]; + return info; +} + +void ReprogControls::initCidMap() +{ + std::unique_lock lock(_cids_populating); + if(_cids_initialized) + return; + uint8_t controls = getControlCount(); + for(uint8_t i = 0; i < controls; i++) { + auto info = getControlInfo(i); + _cids.emplace(info.controlID, info); + } + _cids_initialized = true; +} + +const std::map& + ReprogControls::getControls() const +{ + return _cids; +} + +ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) +{ + if(!_cids_initialized) + initCidMap(); + + auto it = _cids.find(cid); + if(it == _cids.end()) + throw Error(Error::InvalidArgument); + else + return it->second; +} + +ReprogControls::ControlInfo ReprogControls::getControlReporting(uint16_t cid) +{ + // Emulate this function, only Reprog controls v4 supports this + auto info = getControlIdInfo(cid); + + ControlInfo report{}; + report.controlID = cid; + report.flags = 0; + if(info.flags & TemporaryDivertable) + report.flags |= TemporaryDiverted; + if(info.flags & PersisentlyDivertable) + report.flags |= PersistentlyDiverted; + if(info.additionalFlags & RawXY) + report.flags |= RawXYDiverted; + + return report; +} + +void ReprogControls::setControlReporting(uint8_t cid, ControlInfo info) +{ + // This function does not exist pre-v4 and cannot be emulated, ignore. + (void)cid; (void)info; // Suppress unused warnings +} + +std::set ReprogControls::divertedButtonEvent( + const hidpp::Report& report) +{ + assert(report.function() == DivertedButtonEvent); + std::set buttons; + uint8_t cids = std::distance(report.paramBegin(), report.paramEnd())/2; + for(uint8_t i = 0; i < cids; i++) { + uint16_t cid = report.paramBegin()[2*i + 1]; + cid |= report.paramBegin()[2*i] << 8; + if(cid) + buttons.insert(cid); + else + break; + } + return buttons; +} + +ReprogControls::Move ReprogControls::divertedRawXYEvent(const hidpp::Report + &report) +{ + assert(report.function() == DivertedRawXYEvent); + Move move{}; + move.x = report.paramBegin()[1]; + move.x |= report.paramBegin()[0] << 8; + move.y = report.paramBegin()[3]; + move.y |= report.paramBegin()[2] << 8; + return move; +} + +ReprogControls::ControlInfo ReprogControlsV4::getControlReporting(uint16_t cid) +{ + std::vector params(2); + ControlInfo info{}; + params[0] = (cid >> 8) & 0xff; + params[1] = cid & 0xff; + auto response = callFunction(GetControlReporting, params); + + info.controlID = response[1]; + info.controlID |= response[0] << 8; + info.flags = response[2]; + return info; +} + +void ReprogControlsV4::setControlReporting(uint8_t cid, ControlInfo info) +{ + std::vector params(5); + params[0] = (cid >> 8) & 0xff; + params[1] = cid & 0xff; + params[2] = info.flags; + params[3] = (info.controlID >> 8) & 0xff; + params[4] = info.controlID & 0xff; + callFunction(SetControlReporting, params); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/ReprogControls.h b/src/logid/backend/hidpp20/features/ReprogControls.h new file mode 100644 index 0000000..a62b464 --- /dev/null +++ b/src/logid/backend/hidpp20/features/ReprogControls.h @@ -0,0 +1,172 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H +#define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H + +#include + +#include "../feature_defs.h" +#include "../Feature.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class ReprogControls : public Feature + { + public: + enum Function { + GetControlCount = 0, + GetControlInfo = 1, + GetControlReporting = 2, + SetControlReporting = 3 + }; + enum Event { + DivertedButtonEvent = 0, + DivertedRawXYEvent = 1 + }; + + struct ControlInfo + { + uint16_t controlID; + uint16_t taskID; + uint8_t flags; + uint8_t position; // F key position, 0 if not an Fx key + uint8_t group; + uint8_t groupMask; + uint8_t additionalFlags; + }; + + enum ControlInfoFlags: uint8_t + { + MouseButton = 1, //Mouse button + FKey = 1<<1, //Fx key + Hotkey = 1<<2, + FnToggle = 1<<3, + ReprogHint = 1<<4, + TemporaryDivertable = 1<<5, + PersisentlyDivertable = 1<<6, + Virtual = 1<<7 + }; + enum ControlInfoAdditionalFlags: uint8_t { + RawXY = 1<<0 + }; + + enum ControlReportingFlags: uint8_t { + TemporaryDiverted = 1<<0, + ChangeTemporaryDivert = 1<<1, + PersistentlyDiverted = 1<<2, + ChangePersistentDivert = 1<<3, + RawXYDiverted = 1<<4, + ChangeRawXYDivert = 1<<5 + }; + + struct Move + { + int16_t x; + int16_t y; + }; + + static const uint16_t ID = FeatureID::REPROG_CONTROLS; + virtual uint16_t getID() { return ID; } + + virtual bool supportsRawXY() { return false; } + + explicit ReprogControls(Device* dev); + + virtual uint8_t getControlCount(); + + virtual ControlInfo getControlInfo(uint8_t cid); + + virtual ControlInfo getControlIdInfo(uint16_t cid); + + virtual void initCidMap(); + + const std::map& getControls() const; + + // Onlu controlId and flags will be set + virtual ControlInfo getControlReporting(uint16_t cid); + + // Only controlId (for remap) and flags will be read + virtual void setControlReporting(uint8_t cid, ControlInfo info); + + static std::set divertedButtonEvent(const hidpp::Report& + report); + + static Move divertedRawXYEvent(const hidpp::Report& report); + + static std::shared_ptr autoVersion(Device *dev); + protected: + ReprogControls(Device* dev, uint16_t _id); + std::map _cids; + bool _cids_initialized = false; + std::mutex _cids_populating; + }; + + class ReprogControlsV2 : public ReprogControls + { + public: + static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2; + virtual uint16_t getID() { return ID; } + + explicit ReprogControlsV2(Device* dev); + protected: + ReprogControlsV2(Device* dev, uint16_t _id); + }; + + class ReprogControlsV2_2 : public ReprogControlsV2 + { + public: + static const uint16_t ID = FeatureID::REPROG_CONTROLS_V2_2; + virtual uint16_t getID() { return ID; } + + explicit ReprogControlsV2_2(Device* dev); + protected: + ReprogControlsV2_2(Device* dev, uint16_t _id); + }; + + class ReprogControlsV3 : public ReprogControlsV2_2 + { + public: + static const uint16_t ID = FeatureID::REPROG_CONTROLS_V3; + virtual uint16_t getID() { return ID; } + + explicit ReprogControlsV3(Device* dev); + protected: + ReprogControlsV3(Device* dev, uint16_t _id); + }; + + class ReprogControlsV4 : public ReprogControlsV3 + { + public: + static const uint16_t ID = FeatureID::REPROG_CONTROLS_V4; + virtual uint16_t getID() { return ID; } + + bool supportsRawXY() override { return true; } + + ControlInfo getControlReporting(uint16_t cid) override; + + void setControlReporting(uint8_t cid, ControlInfo info) override; + + explicit ReprogControlsV4(Device* dev); + protected: + ReprogControlsV4(Device* dev, uint16_t _id); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H diff --git a/src/logid/backend/hidpp20/features/Reset.cpp b/src/logid/backend/hidpp20/features/Reset.cpp new file mode 100644 index 0000000..3512055 --- /dev/null +++ b/src/logid/backend/hidpp20/features/Reset.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "Reset.h" + +using namespace logid::backend::hidpp20; + +Reset::Reset(Device *device) : Feature(device, ID) +{ +} + +uint16_t Reset::getProfile() +{ + std::vector params(0); + auto results = callFunction(GetProfile, params); + + uint16_t profile = results[1]; + profile |= (results[0] << 8); + return profile; +} + +void Reset::reset(uint16_t profile) +{ + std::vector params(2); + params[0] = (profile >> 8) & 0xff; + params[1] = profile & 0xff; + callFunction(ResetToProfile, params); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/Reset.h b/src/logid/backend/hidpp20/features/Reset.h new file mode 100644 index 0000000..a3b6e14 --- /dev/null +++ b/src/logid/backend/hidpp20/features/Reset.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_RESET_H +#define LOGID_BACKEND_HIDPP20_FEATURE_RESET_H + +#include "../Feature.h" +#include "../feature_defs.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class Reset : public Feature + { + public: + static const uint16_t ID = FeatureID::RESET; + virtual uint16_t getID() { return ID; } + + enum Function : uint8_t + { + GetProfile = 0, + ResetToProfile = 1 + }; + + explicit Reset(Device* device); + + uint16_t getProfile(); + void reset(uint16_t profile = 0); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_RESET_H diff --git a/src/logid/backend/hidpp20/features/Root.cpp b/src/logid/backend/hidpp20/features/Root.cpp new file mode 100644 index 0000000..f1aa724 --- /dev/null +++ b/src/logid/backend/hidpp20/features/Root.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "Root.h" +#include "../Error.h" + +using namespace logid::backend::hidpp20; + +Root::Root(Device* dev) : Feature(dev, ID) +{ +} + +std::vector _genGetFeatureParams(uint16_t feature_id) +{ + std::vector params(2); + params[0] = feature_id & 0xff; + params[1] = (feature_id >> 8) & 0xff; + return params; +} + +feature_info _genGetFeatureInfo(uint16_t feature_id, + std::vector response) +{ + feature_info info{}; + info.feature_id = response[0]; + + if(!info.feature_id) + throw UnsupportedFeature(feature_id); + + info.hidden = response[1] & Root::FeatureFlag::Hidden; + info.obsolete = response[1] & Root::FeatureFlag::Obsolete; + info.internal = response[1] & Root::FeatureFlag::Internal; + + return info; +} + +feature_info Root::getFeature(uint16_t feature_id) +{ + auto params = _genGetFeatureParams(feature_id); + try { + auto response = this->callFunction(Root::Function::GetFeature, params); + return _genGetFeatureInfo(feature_id, response); + } catch(Error& e) { + if(e.code() == Error::InvalidFeatureIndex) + throw UnsupportedFeature(feature_id); + throw e; + } +} + +std::tuple Root::getVersion() +{ + std::vector params(0); + auto response = this->callFunction(Function::Ping, params); + + return std::make_tuple(response[0], response[1]); +} + +EssentialRoot::EssentialRoot(hidpp::Device* dev) : EssentialFeature(dev, ID) +{ +} + +feature_info EssentialRoot::getFeature(uint16_t feature_id) +{ + auto params = _genGetFeatureParams(feature_id); + try { + auto response = this->callFunction(Root::Function::GetFeature, params); + return _genGetFeatureInfo(feature_id, response); + } catch(Error& e) { + if(e.code() == Error::InvalidFeatureIndex) + throw UnsupportedFeature(feature_id); + throw e; + } +} + +std::tuple EssentialRoot::getVersion() +{ + std::vector params(0); + auto response = this->callFunction(Root::Function::Ping, params); + + return std::make_tuple(response[0], response[1]); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/Root.h b/src/logid/backend/hidpp20/features/Root.h new file mode 100644 index 0000000..6d699aa --- /dev/null +++ b/src/logid/backend/hidpp20/features/Root.h @@ -0,0 +1,68 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H +#define LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H + +#include "../Feature.h" +#include "../EssentialFeature.h" +#include "../feature_defs.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class Root : public Feature + { + public: + static const uint16_t ID = FeatureID::ROOT; + virtual uint16_t getID() { return ID; } + + enum Function : uint8_t + { + GetFeature = 0, + Ping = 1 + }; + + explicit Root(Device* device); + + feature_info getFeature (uint16_t feature_id); + std::tuple getVersion(); + + enum FeatureFlag : uint8_t + { + Obsolete = 1<<7, + Hidden = 1<<6, + Internal = 1<<5 + }; + }; + + class EssentialRoot : public EssentialFeature + { + public: + static const uint16_t ID = FeatureID::ROOT; + virtual uint16_t getID() { return ID; } + + explicit EssentialRoot(hidpp::Device* device); + + feature_info getFeature(uint16_t feature_id); + std::tuple getVersion(); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/SmartShift.cpp b/src/logid/backend/hidpp20/features/SmartShift.cpp new file mode 100644 index 0000000..2170f10 --- /dev/null +++ b/src/logid/backend/hidpp20/features/SmartShift.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "SmartShift.h" + +using namespace logid::backend::hidpp20; + +SmartShift::SmartShift(Device* dev) : Feature(dev, ID) +{ +} + +SmartShift::SmartshiftStatus SmartShift::getStatus() +{ + std::vector params(0); + SmartshiftStatus status{}; + auto response = callFunction(GetStatus, params); + status.active = response[0]-1; + status.autoDisengage = response[1]; + status.defaultAutoDisengage = response[2]; + return status; +} + +void SmartShift::setStatus(SmartshiftStatus status) +{ + std::vector params(3); + if(status.setActive) + params[0] = status.active + 1; + if(status.setAutoDisengage) + params[1] = status.autoDisengage; + if(status.setDefaultAutoDisengage) + params[2] = status.defaultAutoDisengage; + callFunction(SetStatus, params); +} \ No newline at end of file diff --git a/src/logid/backend/hidpp20/features/SmartShift.h b/src/logid/backend/hidpp20/features/SmartShift.h new file mode 100644 index 0000000..92f4fb1 --- /dev/null +++ b/src/logid/backend/hidpp20/features/SmartShift.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H +#define LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H + +#include "../feature_defs.h" +#include "../Feature.h" + +namespace logid { +namespace backend { +namespace hidpp20 +{ + class SmartShift : public Feature + { + public: + static const uint16_t ID = FeatureID::SMART_SHIFT; + virtual uint16_t getID() { return ID; } + + enum Function { + GetStatus = 0, + SetStatus = 1 + }; + + explicit SmartShift(Device* dev); + + struct SmartshiftStatus + { + bool active; + uint8_t autoDisengage; + uint8_t defaultAutoDisengage; + bool setActive, setAutoDisengage, setDefaultAutoDisengage; + }; + + SmartshiftStatus getStatus(); + void setStatus(SmartshiftStatus status); + }; +}}} + +#endif //LOGID_BACKEND_HIDPP20_FEATURE_SMARTSHIFT_H diff --git a/src/logid/backend/raw/DeviceMonitor.cpp b/src/logid/backend/raw/DeviceMonitor.cpp new file mode 100644 index 0000000..34d0cb8 --- /dev/null +++ b/src/logid/backend/raw/DeviceMonitor.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "DeviceMonitor.h" +#include "../../util/task.h" +#include "../../util/log.h" +#include "RawDevice.h" +#include "../hidpp/Device.h" + +#include +#include + +extern "C" +{ +#include +#include +} + +using namespace logid::backend::raw; + +DeviceMonitor::DeviceMonitor() +{ + if(-1 == pipe(_pipe)) + throw std::system_error(errno, std::system_category(), + "pipe creation failed"); + + _udev_context = udev_new(); + if(!_udev_context) + throw std::runtime_error("udev_new failed"); +} + +DeviceMonitor::~DeviceMonitor() +{ + this->stop(); + + udev_unref(_udev_context); + + for(int i : _pipe) + close(i); +} + +void DeviceMonitor::run() +{ + int ret; + std::lock_guard lock(_running); + + struct udev_monitor* monitor = udev_monitor_new_from_netlink(_udev_context, + "udev"); + if(!monitor) + throw std::runtime_error("udev_monitor_new_from_netlink failed"); + + ret = udev_monitor_filter_add_match_subsystem_devtype(monitor, "hidraw", + nullptr); + if (0 != ret) + throw std::system_error (-ret, std::system_category(), + "udev_monitor_filter_add_match_subsystem_devtype"); + + ret = udev_monitor_enable_receiving(monitor); + if(0 != ret) + throw std::system_error(-ret, std::system_category(), + "udev_moniotr_enable_receiving"); + + this->enumerate(); + + int fd = udev_monitor_get_fd(monitor); + + _run_monitor = true; + while (_run_monitor) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(_pipe[0], &fds); + FD_SET(fd, &fds); + + if (-1 == select (std::max (_pipe[0], fd)+1, &fds, nullptr, + nullptr, nullptr)) { + if (errno == EINTR) + continue; + throw std::system_error (errno, std::system_category(), + "udev_monitor select"); + } + + if (FD_ISSET(fd, &fds)) { + struct udev_device *device = udev_monitor_receive_device(monitor); + std::string action = udev_device_get_action(device); + std::string devnode = udev_device_get_devnode(device); + + if (action == "add") + task::spawn([this, name=devnode]() { + auto supported_reports = backend::hidpp::getSupportedReports( + RawDevice::getReportDescriptor(name)); + if(supported_reports) + this->addDevice(name); + else + logPrintf(DEBUG, "Unsupported device %s ignored", + name.c_str()); + }, [name=devnode](std::exception& e){ + logPrintf(WARN, "Error adding device %s: %s", + name.c_str(), e.what()); + }); + else if (action == "remove") + task::spawn([this, name=devnode]() { + this->removeDevice(name); + }, [name=devnode](std::exception& e){ + logPrintf(WARN, "Error removing device %s: %s", + name.c_str(), e.what()); + }); + + udev_device_unref (device); + } + if (FD_ISSET(_pipe[0], &fds)) { + char c; + if (-1 == read(_pipe[0], &c, sizeof (char))) + throw std::system_error (errno, std::system_category(), + "read pipe"); + break; + } + } +} + +void DeviceMonitor::stop() +{ + _run_monitor = false; + std::lock_guard lock(_running); +} + +void DeviceMonitor::enumerate() +{ + int ret; + struct udev_enumerate* udev_enum = udev_enumerate_new(_udev_context); + ret = udev_enumerate_add_match_subsystem(udev_enum, "hidraw"); + if(0 != ret) + throw std::system_error(-ret, std::system_category(), + "udev_enumerate_add_match_subsystem"); + + ret = udev_enumerate_scan_devices(udev_enum); + if(0 != ret) + throw std::system_error(-ret, std::system_category(), + "udev_enumerate_scan_devices"); + + struct udev_list_entry* udev_enum_entry; + udev_list_entry_foreach(udev_enum_entry, + udev_enumerate_get_list_entry(udev_enum)) { + const char* name = udev_list_entry_get_name(udev_enum_entry); + + struct udev_device* device = udev_device_new_from_syspath(_udev_context, + name); + if(!device) + throw std::runtime_error("udev_device_new_from_syspath failed"); + + std::string devnode = udev_device_get_devnode(device); + udev_device_unref(device); + + task::spawn([this, name=devnode]() { + auto supported_reports = backend::hidpp::getSupportedReports( + RawDevice::getReportDescriptor(name)); + if(supported_reports) + this->addDevice(name); + else + logPrintf(DEBUG, "Unsupported device %s ignored", + name.c_str()); + }, [name=devnode](std::exception& e){ + logPrintf(WARN, "Error adding device %s: %s", + name.c_str(), e.what()); + }); + } + + udev_enumerate_unref(udev_enum); +} \ No newline at end of file diff --git a/src/logid/backend/raw/DeviceMonitor.h b/src/logid/backend/raw/DeviceMonitor.h new file mode 100644 index 0000000..8239da9 --- /dev/null +++ b/src/logid/backend/raw/DeviceMonitor.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_RAW_DEVICEMONITOR_H +#define LOGID_BACKEND_RAW_DEVICEMONITOR_H + +#include +#include +#include + +extern "C" +{ +#include +} + +namespace logid { +namespace backend { +namespace raw +{ + class DeviceMonitor + { + public: + void enumerate(); + void run(); + void stop(); + protected: + DeviceMonitor(); + ~DeviceMonitor(); + virtual void addDevice(std::string device) = 0; + virtual void removeDevice(std::string device) = 0; + private: + struct udev* _udev_context; + int _pipe[2]; + std::atomic _run_monitor; + std::mutex _running; + }; +}}} + +#endif //LOGID_BACKEND_RAW_DEVICEMONITOR_H \ No newline at end of file diff --git a/src/logid/backend/raw/RawDevice.cpp b/src/logid/backend/raw/RawDevice.cpp new file mode 100644 index 0000000..c7d5ce5 --- /dev/null +++ b/src/logid/backend/raw/RawDevice.cpp @@ -0,0 +1,497 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "RawDevice.h" +#include "../Error.h" +#include "../hidpp/defs.h" +#include "../dj/defs.h" +#include "../../util/log.h" +#include "../hidpp/Report.h" +#include "../../Configuration.h" +#include "../../util/thread.h" +#include "../../util/task.h" +#include "../../util/workqueue.h" + +#include +#include +#include + +#define MAX_DATA_LENGTH 32 + +extern "C" +{ +#include +#include +#include +#include +#include +} + +using namespace logid::backend::raw; +using namespace logid::backend; +using namespace std::chrono; + +bool RawDevice::supportedReport(uint8_t id, uint8_t length) +{ + switch(id) { + case hidpp::ReportType::Short: + return length == (hidpp::ShortParamLength + + hidpp::Report::HeaderLength); + case hidpp::ReportType::Long: + return length == (hidpp::LongParamLength + + hidpp::Report::HeaderLength); + case dj::ReportType::Short: + return length == (dj::ShortParamLength + dj::HeaderLength); + case dj::ReportType::Long: + return length == (dj::LongParamLength + dj::HeaderLength); + default: + return false; + } +} + +RawDevice::RawDevice(std::string path) : _path (std::move(path)), + _continue_listen (false), _continue_respond (false) +{ + int ret; + + _fd = ::open(_path.c_str(), O_RDWR); + if (_fd == -1) + throw std::system_error(errno, std::system_category(), + "RawDevice open failed"); + + hidraw_devinfo devinfo{}; + if (-1 == ::ioctl(_fd, HIDIOCGRAWINFO, &devinfo)) { + int err = errno; + ::close(_fd); + throw std::system_error(err, std::system_category(), + "RawDevice HIDIOCGRAWINFO failed"); + } + _vid = devinfo.vendor; + _pid = devinfo.product; + + char name_buf[256]; + if (-1 == (ret = ::ioctl(_fd, HIDIOCGRAWNAME(sizeof(name_buf)), name_buf) + )) { + int err = errno; + ::close(_fd); + throw std::system_error(err, std::system_category(), + "RawDevice HIDIOCGRAWNAME failed"); + } + _name.assign(name_buf, ret - 1); + + _rdesc = getReportDescriptor(_fd); + + if (-1 == ::pipe(_pipe)) { + int err = errno; + close(_fd); + throw std::system_error(err, std::system_category(), + "RawDevice pipe open failed"); + } + + _continue_listen = false; +} + +RawDevice::~RawDevice() +{ + if(_fd != -1) + { + ::close(_fd); + ::close(_pipe[0]); + ::close(_pipe[1]); + } +} +std::string RawDevice::hidrawPath() const +{ + return _path; +} + +std::string RawDevice::name() const +{ + return _name; +} + +uint16_t RawDevice::vendorId() const +{ + return _vid; +} + +uint16_t RawDevice::productId() const +{ + return _pid; +} + +std::vector RawDevice::getReportDescriptor(std::string path) +{ + int fd = ::open(path.c_str(), O_RDWR); + if (fd == -1) + throw std::system_error(errno, std::system_category(), + "open failed"); + + auto rdesc = getReportDescriptor(fd); + ::close(fd); + return rdesc; +} + +std::vector RawDevice::getReportDescriptor(int fd) +{ + hidraw_report_descriptor rdesc{}; + if (-1 == ::ioctl(fd, HIDIOCGRDESCSIZE, &rdesc.size)) { + int err = errno; + ::close(fd); + throw std::system_error(err, std::system_category(), + "RawDevice HIDIOCGRDESCSIZE failed"); + } + if (-1 == ::ioctl(fd, HIDIOCGRDESC, &rdesc)) { + int err = errno; + ::close(fd); + throw std::system_error(err, std::system_category(), + "RawDevice HIDIOCGRDESC failed"); + } + return std::vector(rdesc.value, rdesc.value + rdesc.size); +} + +std::vector RawDevice::reportDescriptor() const +{ + return _rdesc; +} + +std::vector RawDevice::sendReport(const std::vector& report) +{ + /* If the listener will stop, handle I/O manually. + * Otherwise, push to queue and wait for result. */ + if(_continue_listen) { + std::mutex send_report; + std::unique_lock lock(send_report); + std::condition_variable cv; + bool top_of_queue = false; + auto task = std::make_shared()>> + ( [this, report, &cv, &top_of_queue] () { + top_of_queue = true; + cv.notify_all(); + return this->_respondToReport(report); + }); + auto f = task->get_future(); + _io_queue.push(task); + cv.wait(lock, [&top_of_queue]{ return top_of_queue; }); + auto status = f.wait_for(global_config->ioTimeout()); + if(status == std::future_status::timeout) { + _continue_respond = false; + interruptRead(); + return f.get(); // Expecting an error, but it could work + } + return f.get(); + } + else { + std::vector response; + std::exception_ptr _exception; + std::shared_ptr t = std::make_shared( + [this, report, &response]() { + response = _respondToReport(report); + }, [&_exception](std::exception& e) { + try { + throw e; + } catch(std::exception& e) { + _exception = std::make_exception_ptr(e); + } + }); + global_workqueue->queue(t); + t->waitStart(); + auto status = t->waitFor(global_config->ioTimeout()); + if(_exception) + std::rethrow_exception(_exception); + if(status == std::future_status::timeout) { + _continue_respond = false; + interruptRead(); + t->wait(); + if(_exception) + std::rethrow_exception(_exception); + throw TimeoutError(); + } else + return response; + } +} + +// DJ commands are not systematically acknowledged, do not expect a result. +void RawDevice::sendReportNoResponse(const std::vector& report) +{ + /* If the listener will stop, handle I/O manually. + * Otherwise, push to queue and wait for result. */ + if(_continue_listen) { + auto task = std::make_shared()>> + ([this, report]() { + this->_sendReport(report); + return std::vector(); + }); + auto f = task->get_future(); + _io_queue.push(task); + f.get(); + } + else + _sendReport(report); +} + +std::vector RawDevice::_respondToReport + (const std::vector& request) +{ + _sendReport(request); + _continue_respond = true; + + auto start_point = std::chrono::steady_clock::now(); + + while(_continue_respond) { + std::vector response; + auto current_point = std::chrono::steady_clock::now(); + auto timeout = global_config->ioTimeout() - std::chrono::duration_cast + (current_point - start_point); + if(timeout.count() <= 0) + throw TimeoutError(); + _readReport(response, MAX_DATA_LENGTH, timeout); + + if(!_continue_respond) + throw TimeoutError(); + + // All reports have the device index at byte 2 + if(response[1] != request[1]) { + if(_continue_listen) + this->_handleEvent(response); + continue; + } + + if(hidpp::ReportType::Short == request[0] || + hidpp::ReportType::Long == request[0]) { + if(hidpp::ReportType::Short != response[0] && + hidpp::ReportType::Long != response[0]) { + if(_continue_listen) + this->_handleEvent(response); + continue; + } + + // Error; leave to device to handle + if(response[2] == 0x8f || response[2] == 0xff) + return response; + + bool others_match = true; + for(int i = 2; i < 4; i++) + if(response[i] != request[i]) + others_match = false; + + if(others_match) + return response; + } else if(dj::ReportType::Short == request[0] || + dj::ReportType::Long == request[0]) { + //Error; leave to device ot handle + if(0x7f == response[2]) + return response; + else if(response[2] == request[2]) + return response; + } + + if(_continue_listen) + this->_handleEvent(response); + } + + return {}; +} + +int RawDevice::_sendReport(const std::vector& report) +{ + std::lock_guard lock(_dev_io); + if(logid::global_loglevel <= LogLevel::RAWREPORT) { + printf("[RAWREPORT] %s OUT: ", _path.c_str()); + for(auto &i : report) + printf("%02x ", i); + printf("\n"); + } + + assert(supportedReport(report[0], report.size())); + + int ret = ::write(_fd, report.data(), report.size()); + if(ret == -1) { + ///TODO: This seems like a hacky solution + // Try again before failing + ret = ::write(_fd, report.data(), report.size()); + if(ret == -1) + throw std::system_error(errno, std::system_category(), + "_sendReport write failed"); + } + + return ret; +} + +int RawDevice::_readReport(std::vector &report, + std::size_t maxDataLength) +{ + return _readReport(report, maxDataLength, global_config->ioTimeout()); +} + +int RawDevice::_readReport(std::vector &report, + std::size_t maxDataLength, std::chrono::milliseconds timeout) +{ + std::lock_guard lock(_dev_io); + int ret; + report.resize(maxDataLength); + + timeval timeout_tv{}; + timeout_tv.tv_sec = duration_cast(global_config->ioTimeout()) + .count(); + timeout_tv.tv_usec = duration_cast( + global_config->ioTimeout()).count() % + duration_cast(seconds(1)).count(); + + auto timeout_ms = duration_cast(timeout).count(); + + fd_set fds; + do { + FD_ZERO(&fds); + FD_SET(_fd, &fds); + FD_SET(_pipe[0], &fds); + + ret = select(std::max(_fd, _pipe[0]) + 1, + &fds, nullptr, nullptr, + (timeout_ms > 0 ? nullptr : &timeout_tv)); + } while(ret == -1 && errno == EINTR); + + if(ret == -1) + throw std::system_error(errno, std::system_category(), + "_readReport select failed"); + + if(FD_ISSET(_fd, &fds)) { + ret = read(_fd, report.data(), report.size()); + if(ret == -1) + throw std::system_error(errno, std::system_category(), + "_readReport read failed"); + report.resize(ret); + } + + if(FD_ISSET(_pipe[0], &fds)) { + char c; + ret = read(_pipe[0], &c, sizeof(char)); + if(ret == -1) + throw std::system_error(errno, std::system_category(), + "_readReport read pipe failed"); + } + + if(0 == ret) + throw backend::TimeoutError(); + + if(logid::global_loglevel <= LogLevel::RAWREPORT) { + printf("[RAWREPORT] %s IN: ", _path.c_str()); + for(auto &i : report) + printf("%02x ", i); + printf("\n"); + } + + return ret; +} + +void RawDevice::interruptRead() +{ + char c = 0; + if(-1 == write(_pipe[1], &c, sizeof(char))) + throw std::system_error(errno, std::system_category(), + "interruptRead write pipe failed"); + + // Ensure I/O has halted + std::lock_guard lock(_dev_io); +} + +void RawDevice::listen() +{ + std::lock_guard lock(_listening); + _continue_listen = true; + _listen_condition.notify_all(); + while(_continue_listen) { + while(!_io_queue.empty()) { + auto task = _io_queue.front(); + (*task)(); + _io_queue.pop(); + } + std::vector report; + _readReport(report, MAX_DATA_LENGTH); + + this->_handleEvent(report); + } + + // Listener is stopped, handle I/O queue + while(!_io_queue.empty()) { + auto task = _io_queue.front(); + (*task)(); + _io_queue.pop(); + } + + _continue_listen = false; +} + +void RawDevice::listenAsync() +{ + std::mutex listen_check; + std::unique_lock check_lock(listen_check); + thread::spawn({[this]() { listen(); }}); + + // Block until RawDevice is listening + _listen_condition.wait(check_lock, [this](){ + return (bool)_continue_listen; + }); +} + +void RawDevice::stopListener() +{ + _continue_listen = false; + interruptRead(); +} + +void RawDevice::addEventHandler(const std::string& nickname, + const std::shared_ptr& handler) +{ + std::unique_lock lock(_event_handler_lock); + auto it = _event_handlers.find(nickname); + assert(it == _event_handlers.end()); + assert(handler); + _event_handlers.emplace(nickname, handler); +} + +void RawDevice::removeEventHandler(const std::string &nickname) +{ + std::unique_lock lock(_event_handler_lock); + _event_handlers.erase(nickname); +} + +const std::map>& +RawDevice::eventHandlers() +{ + std::unique_lock lock(_event_handler_lock); + return _event_handlers; +} + +void RawDevice::_handleEvent(std::vector &report) +{ + std::unique_lock lock(_event_handler_lock); + for(auto& handler : _event_handlers) + if(handler.second->condition(report)) + handler.second->callback(report); +} + +bool RawDevice::isListening() +{ + bool ret = _listening.try_lock(); + + if(ret) + _listening.unlock(); + + return !ret; +} diff --git a/src/logid/backend/raw/RawDevice.h b/src/logid/backend/raw/RawDevice.h new file mode 100644 index 0000000..e625944 --- /dev/null +++ b/src/logid/backend/raw/RawDevice.h @@ -0,0 +1,102 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_RAWDEVICE_H +#define LOGID_BACKEND_RAWDEVICE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "../../util/mutex_queue.h" + +namespace logid { +namespace backend { +namespace raw +{ + class RawDevice + { + public: + static bool supportedReport(uint8_t id, uint8_t length); + + explicit RawDevice(std::string path); + ~RawDevice(); + std::string hidrawPath() const; + + std::string name() const; + uint16_t vendorId() const; + uint16_t productId() const; + + static std::vector getReportDescriptor(std::string path); + static std::vector getReportDescriptor(int fd); + std::vector reportDescriptor() const; + + std::vector sendReport(const std::vector& report); + void sendReportNoResponse(const std::vector& report); + void interruptRead(); + + void listen(); + void listenAsync(); + void stopListener(); + bool isListening(); + + void addEventHandler(const std::string& nickname, + const std::shared_ptr& handler); + void removeEventHandler(const std::string& nickname); + const std::map>& + eventHandlers(); + + private: + std::mutex _dev_io, _listening; + std::string _path; + int _fd; + int _pipe[2]; + uint16_t _vid; + uint16_t _pid; + std::string _name; + std::vector _rdesc; + + std::atomic _continue_listen; + std::atomic _continue_respond; + std::condition_variable _listen_condition; + + std::map> + _event_handlers; + std::mutex _event_handler_lock; + void _handleEvent(std::vector& report); + + /* These will only be used internally and processed with a queue */ + int _sendReport(const std::vector& report); + int _readReport(std::vector& report, std::size_t maxDataLength); + int _readReport(std::vector& report, std::size_t maxDataLength, + std::chrono::milliseconds timeout); + + std::vector _respondToReport(const std::vector& + request); + + mutex_queue()>>> + _io_queue; + }; +}}} + +#endif //LOGID_BACKEND_RAWDEVICE_H \ No newline at end of file diff --git a/src/logid/backend/raw/defs.h b/src/logid/backend/raw/defs.h new file mode 100644 index 0000000..388d9c6 --- /dev/null +++ b/src/logid/backend/raw/defs.h @@ -0,0 +1,37 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_BACKEND_RAW_DEFS_H +#define LOGID_BACKEND_RAW_DEFS_H + +#include +#include +#include + +namespace logid { +namespace backend { +namespace raw +{ + struct RawEventHandler + { + std::function& )> condition; + std::function& )> callback; + }; +}}} + +#endif //LOGID_BACKEND_RAW_DEFS_H \ No newline at end of file diff --git a/src/logid/features/DPI.cpp b/src/logid/features/DPI.cpp new file mode 100644 index 0000000..435cdb8 --- /dev/null +++ b/src/logid/features/DPI.cpp @@ -0,0 +1,140 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include "DPI.h" +#include "../Device.h" +#include "../util/log.h" + +using namespace logid::features; +using namespace logid::backend; + +uint16_t getClosestDPI(hidpp20::AdjustableDPI::SensorDPIList& dpi_list, + uint16_t dpi) +{ + if(dpi_list.isRange) { + const uint16_t min = *std::min_element(dpi_list.dpis.begin(), + dpi_list.dpis.end()); + const uint16_t max = *std::max_element(dpi_list.dpis.begin(), + dpi_list.dpis.end()); + if(!((dpi-min) % dpi_list.dpiStep) && dpi >= min && dpi <= max) + return dpi; + else if(dpi > max) + return max; + else if(dpi < min) + return min; + else + return min + round((double)(dpi-min)/dpi_list.dpiStep)*dpi_list + .dpiStep; + } else { + if(std::find(dpi_list.dpis.begin(), dpi_list.dpis.end(), dpi) + != dpi_list.dpis.end()) + return dpi; + else { + auto it = std::min_element(dpi_list.dpis.begin(), dpi_list.dpis + .end(), [dpi](uint16_t a, uint16_t b) { + return (dpi - a) < (dpi - b); + }); + if(it == dpi_list.dpis.end()) + return 0; + else + return *it; + } + } +} + +DPI::DPI(Device* device) : DeviceFeature(device), _config (device), + _adjustable_dpi (&device->hidpp20()) +{ +} + +void DPI::configure() +{ + const uint8_t sensors = _adjustable_dpi.getSensorCount(); + for(uint8_t i = 0; i < _config.getSensorCount(); i++) { + hidpp20::AdjustableDPI::SensorDPIList dpi_list; + if(_dpi_lists.size() <= i) { + dpi_list = _adjustable_dpi.getSensorDPIList(i); + _dpi_lists.push_back(dpi_list); + } else { + dpi_list = _dpi_lists[i]; + } + if(i < sensors) { + auto dpi = _config.getDPI(i); + if(dpi) { + _adjustable_dpi.setSensorDPI(i, getClosestDPI(dpi_list, + dpi)); + } + } + } +} + +void DPI::listen() +{ +} + +uint16_t DPI::getDPI(uint8_t sensor) +{ + return _adjustable_dpi.getSensorDPI(sensor); +} + +void DPI::setDPI(uint16_t dpi, uint8_t sensor) +{ + hidpp20::AdjustableDPI::SensorDPIList dpi_list; + if(_dpi_lists.size() <= sensor) { + dpi_list = _adjustable_dpi.getSensorDPIList(sensor); + for(std::size_t i = _dpi_lists.size()-1; i <= sensor; i++) { + _dpi_lists.push_back(_adjustable_dpi.getSensorDPIList(i)); + } + } + dpi_list = _dpi_lists[sensor]; + _adjustable_dpi.setSensorDPI(sensor, getClosestDPI(dpi_list, dpi)); +} + +/* Some devices have multiple sensors, but an older config format + * only supports a single DPI. The dpi setting can be an array or + * an integer. + */ +DPI::Config::Config(Device *dev) : DeviceFeature::Config(dev) +{ + try { + auto& config_root = dev->config().getSetting("dpi"); + if(config_root.isNumber()) { + int dpi = config_root; + _dpis.push_back(dpi); + } else if(config_root.isArray()) { + for(int i = 0; i < config_root.getLength(); i++) + _dpis.push_back((int)config_root[i]); + } else { + logPrintf(WARN, "Line %d: dpi is improperly formatted", + config_root.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { + // DPI not configured, use default + } +} + +uint8_t DPI::Config::getSensorCount() +{ + return _dpis.size(); +} + +uint16_t DPI::Config::getDPI(uint8_t sensor) +{ + return _dpis[sensor]; +} diff --git a/src/logid/features/DPI.h b/src/logid/features/DPI.h new file mode 100644 index 0000000..8bc2468 --- /dev/null +++ b/src/logid/features/DPI.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_FEATURE_DPI_H +#define LOGID_FEATURE_DPI_H + +#include "../backend/hidpp20/features/AdjustableDPI.h" +#include "DeviceFeature.h" + +namespace logid { +namespace features +{ + class DPI : public DeviceFeature + { + public: + explicit DPI(Device* dev); + virtual void configure(); + virtual void listen(); + + uint16_t getDPI(uint8_t sensor=0); + void setDPI(uint16_t dpi, uint8_t sensor=0); + + class Config : public DeviceFeature::Config + { + public: + explicit Config(Device* dev); + uint16_t getDPI(uint8_t sensor); + uint8_t getSensorCount(); + protected: + std::vector _dpis; + }; + private: + Config _config; + backend::hidpp20::AdjustableDPI _adjustable_dpi; + std::vector _dpi_lists; + }; + }} + +#endif //LOGID_FEATURE_DPI_H diff --git a/src/logid/features/DeviceFeature.h b/src/logid/features/DeviceFeature.h new file mode 100644 index 0000000..5bccee1 --- /dev/null +++ b/src/logid/features/DeviceFeature.h @@ -0,0 +1,51 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_FEATURES_DEVICEFEATURE_H +#define LOGID_FEATURES_DEVICEFEATURE_H + +#include + +namespace logid { + class Device; +namespace features +{ + class DeviceFeature + { + public: + explicit DeviceFeature(Device* dev) : _device (dev) + { + } + virtual void configure() = 0; + virtual void listen() = 0; + class Config + { + public: + explicit Config(Device* dev) : _device (dev) + { + } + protected: + Device* _device; + }; + + protected: + Device* _device; + }; +}} + +#endif //LOGID_FEATURES_DEVICEFEATURE_H diff --git a/src/logid/features/HiresScroll.cpp b/src/logid/features/HiresScroll.cpp new file mode 100644 index 0000000..e21a8b0 --- /dev/null +++ b/src/logid/features/HiresScroll.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "HiresScroll.h" +#include "../Device.h" + +using namespace logid::features; +using namespace logid::backend; + +HiresScroll::HiresScroll(Device *dev) : DeviceFeature(dev), + _hires_scroll(&dev->hidpp20()), _config(dev) +{ +} + +void HiresScroll::configure() +{ + auto mode = _hires_scroll.getMode(); + mode &= ~_config.getMask(); + mode |= (_config.getMode() & _config.getMask()); + _hires_scroll.setMode(mode); +} + +void HiresScroll::listen() +{ + ///TODO: Map hires scroll events +} + +uint8_t HiresScroll::getMode() +{ + return _hires_scroll.getMode(); +} + +void HiresScroll::setMode(uint8_t mode) +{ + _hires_scroll.setMode(mode); +} + +HiresScroll::Config::Config(Device *dev) : DeviceFeature::Config(dev) +{ + try { + auto& config_root = dev->config().getSetting("hiresscroll"); + if(!config_root.isGroup()) { + logPrintf(WARN, "Line %d: hiresscroll must be a group", + config_root.getSourceLine()); + return; + } + _mode = 0; + _mask = 0; + try { + auto& hires = config_root.lookup("hires"); + if(hires.getType() == libconfig::Setting::TypeBoolean) { + _mask |= hidpp20::HiresScroll::Mode::HiRes; + if(hires) + _mode |= hidpp20::HiresScroll::Mode::HiRes; + } else { + logPrintf(WARN, "Line %d: hires must be a boolean", + hires.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { } + + try { + auto& invert = config_root.lookup("invert"); + if(invert.getType() == libconfig::Setting::TypeBoolean) { + _mask |= hidpp20::HiresScroll::Mode::Inverted; + if(invert) + _mode |= hidpp20::HiresScroll::Mode::Inverted; + } else { + logPrintf(WARN, "Line %d: invert must be a boolean, ignoring.", + invert.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { } + + try { + auto& target = config_root.lookup("target"); + if(target.getType() == libconfig::Setting::TypeBoolean) { + _mask |= hidpp20::HiresScroll::Mode::Target; + if(target) + _mode |= hidpp20::HiresScroll::Mode::Target; + } else { + logPrintf(WARN, "Line %d: target must be a boolean, ignoring.", + target.getSourceLine()); + } + } catch(libconfig::SettingNotFoundException& e) { } + } catch(libconfig::SettingNotFoundException& e) { + // HiresScroll not configured, use default + } +} + +uint8_t HiresScroll::Config::getMode() const +{ + return _mode; +} + +uint8_t HiresScroll::Config::getMask() const +{ + return _mask; +} \ No newline at end of file diff --git a/src/logid/features/HiresScroll.h b/src/logid/features/HiresScroll.h new file mode 100644 index 0000000..7e81c97 --- /dev/null +++ b/src/logid/features/HiresScroll.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_FEATURE_HIRESSCROLL_H +#define LOGID_FEATURE_HIRESSCROLL_H + +#include "../backend/hidpp20/features/HiresScroll.h" +#include "DeviceFeature.h" + +namespace logid { +namespace features +{ + class HiresScroll : public DeviceFeature + { + public: + explicit HiresScroll(Device* dev); + virtual void configure(); + virtual void listen(); + + uint8_t getMode(); + void setMode(uint8_t mode); + + class Config : public DeviceFeature::Config + { + public: + explicit Config(Device* dev); + uint8_t getMode() const; + uint8_t getMask() const; + protected: + uint8_t _mode; + uint8_t _mask; + }; + private: + backend::hidpp20::HiresScroll _hires_scroll; + Config _config; + }; +}} + +#endif //LOGID_FEATURE_HIRESSCROLL_H diff --git a/src/logid/features/RemapButton.cpp b/src/logid/features/RemapButton.cpp new file mode 100644 index 0000000..9f8f813 --- /dev/null +++ b/src/logid/features/RemapButton.cpp @@ -0,0 +1,203 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "../Device.h" +#include "RemapButton.h" +#include "../backend/hidpp20/Error.h" + +using namespace logid::features; +using namespace logid::backend; +using namespace logid::actions; + +#define HIDPP20_REPROG_REBIND (hidpp20::ReprogControls::ChangeTemporaryDivert \ +| hidpp20::ReprogControls::ChangeRawXYDivert) + +#define EVENTHANDLER_NAME "REMAP_BUTTON" + +RemapButton::RemapButton(Device *dev): DeviceFeature(dev), _config (dev), + _reprog_controls (hidpp20::ReprogControls::autoVersion(&dev->hidpp20())) +{ + _reprog_controls->initCidMap(); + + if(global_loglevel <= DEBUG) { + #define FLAG(x) control.second.flags & hidpp20::ReprogControls::x ? \ + "YES" : "" + #define ADDITIONAL_FLAG(x) control.second.additionalFlags & \ + hidpp20::ReprogControls::x ? "YES" : "" + + // Print CIDs, originally by zv0n + logPrintf(DEBUG, "%s:%d remappable buttons:", + dev->hidpp20().devicePath().c_str(), + dev->hidpp20().deviceIndex()); + logPrintf(DEBUG, "CID | reprog? | fn key? | mouse key? | " + "gesture support?"); + for(const auto & control : _reprog_controls->getControls()) + logPrintf(DEBUG, "0x%02x | %-7s | %-7s | %-10s | %s", + control.first, FLAG(TemporaryDivertable), FLAG(FKey), + FLAG(MouseButton), ADDITIONAL_FLAG(RawXY)); + #undef FLAG + } +} + +RemapButton::~RemapButton() +{ + _device->hidpp20().removeEventHandler(EVENTHANDLER_NAME); +} + +void RemapButton::configure() +{ + ///TODO: DJ reporting trickery if cannot be remapped + for(const auto& i : _config.buttons()) { + hidpp20::ReprogControls::ControlInfo info{}; + try { + info = _reprog_controls->getControlIdInfo(i.first); + } catch(hidpp20::Error& e) { + if(e.code() == hidpp20::Error::InvalidArgument) { + logPrintf(WARN, "%s: CID 0x%02x does not exist.", + _device->name().c_str(), i.first); + continue; + } + throw e; + } + + if((i.second->reprogFlags() & hidpp20::ReprogControls::RawXYDiverted) && + (!_reprog_controls->supportsRawXY() || !(info.additionalFlags & + hidpp20::ReprogControls::RawXY))) + logPrintf(WARN, "%s: Cannot divert raw XY movements for CID " + "0x%02x", _device->name().c_str(), i.first); + + hidpp20::ReprogControls::ControlInfo report{}; + report.controlID = i.first; + report.flags = HIDPP20_REPROG_REBIND; + report.flags |= i.second->reprogFlags(); + _reprog_controls->setControlReporting(i.first, report); + } +} + +void RemapButton::listen() +{ + if(_device->hidpp20().eventHandlers().find(EVENTHANDLER_NAME) == + _device->hidpp20().eventHandlers().end()) { + auto handler = std::make_shared(); + handler->condition = [index=_reprog_controls->featureIndex()] + (hidpp::Report& report)->bool { + return (report.feature() == index) && ((report.function() == + hidpp20::ReprogControls::DivertedButtonEvent) || (report + .function() == hidpp20::ReprogControls::DivertedRawXYEvent)); + }; + + handler->callback = [this](hidpp::Report& report)->void { + if(report.function() == + hidpp20::ReprogControls::DivertedButtonEvent) + this->_buttonEvent(_reprog_controls->divertedButtonEvent( + report)); + else { // RawXY + auto divertedXY = _reprog_controls->divertedRawXYEvent(report); + for(const auto& button : this->_config.buttons()) + if(button.second->pressed()) + button.second->move(divertedXY.x, divertedXY.y); + } + }; + + _device->hidpp20().addEventHandler(EVENTHANDLER_NAME, handler); + } +} + +void RemapButton::_buttonEvent(const std::set& new_state) +{ + // Ensure I/O doesn't occur while updating button state + std::lock_guard lock(_button_lock); + + // Press all added buttons + for(const auto& i : new_state) { + auto old_i = _pressed_buttons.find(i); + if(old_i != _pressed_buttons.end()) { + _pressed_buttons.erase(old_i); + } else { + auto action = _config.buttons().find(i); + if(action != _config.buttons().end()) + action->second->press(); + } + } + + // Release all removed buttons + for(auto& i : _pressed_buttons) { + auto action = _config.buttons().find(i); + if(action != _config.buttons().end()) + action->second->release(); + } + + _pressed_buttons = new_state; +} + +RemapButton::Config::Config(Device *dev) : DeviceFeature::Config(dev) +{ + try { + auto& config_root = dev->config().getSetting("buttons"); + if(!config_root.isList()) { + logPrintf(WARN, "Line %d: buttons must be a list.", + config_root.getSourceLine()); + return; + } + int button_count = config_root.getLength(); + for(int i = 0; i < button_count; i++) + _parseButton(config_root[i]); + } catch(libconfig::SettingNotFoundException& e) { + // buttons not configured, use default + } +} + +void RemapButton::Config::_parseButton(libconfig::Setting &setting) +{ + if(!setting.isGroup()) { + logPrintf(WARN, "Line %d: button must be an object, ignoring.", + setting.getSourceLine()); + return; + } + + uint16_t cid; + try { + auto& cid_setting = setting.lookup("cid"); + if(!cid_setting.isNumber()) { + logPrintf(WARN, "Line %d: cid must be a number, ignoring.", + cid_setting.getSourceLine()); + return; + } + cid = (int)cid_setting; + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: cid is required, ignoring.", + setting.getSourceLine()); + return; + } + + try { + _buttons.emplace(cid, Action::makeAction(_device, + setting.lookup("action"))); + } catch(libconfig::SettingNotFoundException& e) { + logPrintf(WARN, "Line %d: action is required, ignoring.", + setting.getSourceLine()); + } catch(InvalidAction& e) { + logPrintf(WARN, "Line %d: %s is not a valid action, ignoring.", + setting["action"].getSourceLine(), e.what()); + } +} + +const std::map>& RemapButton::Config::buttons() +{ + return _buttons; +} \ No newline at end of file diff --git a/src/logid/features/RemapButton.h b/src/logid/features/RemapButton.h new file mode 100644 index 0000000..f4aa4b6 --- /dev/null +++ b/src/logid/features/RemapButton.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_FEATURE_REMAPBUTTON_H +#define LOGID_FEATURE_REMAPBUTTON_H + +#include "../backend/hidpp20/features/ReprogControls.h" +#include "DeviceFeature.h" +#include "../actions/Action.h" + +namespace logid { +namespace features +{ + class RemapButton : public DeviceFeature + { + public: + explicit RemapButton(Device* dev); + ~RemapButton(); + virtual void configure(); + virtual void listen(); + + class Config : public DeviceFeature::Config + { + public: + explicit Config(Device* dev); + const std::map>& + buttons(); + protected: + void _parseButton(libconfig::Setting& setting); + std::map> _buttons; + }; + private: + void _buttonEvent(const std::set& new_state); + Config _config; + std::shared_ptr _reprog_controls; + std::set _pressed_buttons; + std::mutex _button_lock; + }; +}} + +#endif //LOGID_FEATURE_REMAPBUTTON_H diff --git a/src/logid/features/SmartShift.cpp b/src/logid/features/SmartShift.cpp new file mode 100644 index 0000000..fd357f0 --- /dev/null +++ b/src/logid/features/SmartShift.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "SmartShift.h" +#include "../Device.h" +#include "../util/log.h" + +using namespace logid::features; +using namespace logid::backend; + +SmartShift::SmartShift(Device* device) : DeviceFeature(device), _config + (device), _smartshift(&device->hidpp20()) +{ +} + +void SmartShift::configure() +{ + _smartshift.setStatus(_config.getSettings()); +} + +void SmartShift::listen() +{ +} + +hidpp20::SmartShift::SmartshiftStatus SmartShift::getStatus() +{ + return _smartshift.getStatus(); +} + +void SmartShift::setStatus(backend::hidpp20::SmartShift::SmartshiftStatus + status) +{ + _smartshift.setStatus(status); +} + +SmartShift::Config::Config(Device *dev) : DeviceFeature::Config(dev), _status() +{ + try { + auto& config_root = dev->config().getSetting("smartshift"); + if(!config_root.isGroup()) { + logPrintf(WARN, "Line %d: smartshift must be an object", + config_root.getSourceLine()); + return; + } + _status.setActive = config_root.lookupValue("on", _status.active); + int tmp; + _status.setAutoDisengage = config_root.lookupValue("threshold", tmp); + if(_status.setAutoDisengage) + _status.autoDisengage = tmp; + _status.setDefaultAutoDisengage = config_root.lookupValue + ("default_threshold", tmp); + if(_status.setDefaultAutoDisengage) + _status.defaultAutoDisengage = tmp; + } catch(libconfig::SettingNotFoundException& e) { + // SmartShift not configured, use default + } +} + +hidpp20::SmartShift::SmartshiftStatus SmartShift::Config::getSettings() +{ + return _status; +} \ No newline at end of file diff --git a/src/logid/features/SmartShift.h b/src/logid/features/SmartShift.h new file mode 100644 index 0000000..06bb280 --- /dev/null +++ b/src/logid/features/SmartShift.h @@ -0,0 +1,51 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_FEATURE_SMARTSHIFT_H +#define LOGID_FEATURE_SMARTSHIFT_H + +#include "../backend/hidpp20/features/SmartShift.h" +#include "DeviceFeature.h" + +namespace logid { +namespace features +{ + class SmartShift : public DeviceFeature + { + public: + explicit SmartShift(Device* dev); + virtual void configure(); + virtual void listen(); + + backend::hidpp20::SmartShift::SmartshiftStatus getStatus(); + void setStatus(backend::hidpp20::SmartShift::SmartshiftStatus status); + + class Config : public DeviceFeature::Config + { + public: + explicit Config(Device* dev); + backend::hidpp20::SmartShift::SmartshiftStatus getSettings(); + protected: + backend::hidpp20::SmartShift::SmartshiftStatus _status; + }; + private: + Config _config; + backend::hidpp20::SmartShift _smartshift; + }; +}} + +#endif //LOGID_FEATURE_SMARTSHIFT_H diff --git a/src/logid/hidpp b/src/logid/hidpp deleted file mode 160000 index c64ec3a..0000000 --- a/src/logid/hidpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c64ec3a12b203024e48993c1aadf4618b3dbd37c diff --git a/src/logid/logid.cpp b/src/logid/logid.cpp index e112e50..ddb77ec 100644 --- a/src/logid/logid.cpp +++ b/src/logid/logid.cpp @@ -1,137 +1,194 @@ -#include -#include -#include -#include -#include -#include -#include +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + #include #include #include +#include -#include "util.h" -#include "Device.h" -#include "Actions.h" -#include "Configuration.h" -#include "EvdevDevice.h" -#include "DeviceFinder.h" +#include "util/log.h" +#include "DeviceManager.h" +#include "logid.h" +#include "InputDevice.h" +#include "util/workqueue.h" -#define evdev_name "logid" +#define LOGID_VIRTUAL_INPUT_NAME "LogiOps Virtual Input" #define DEFAULT_CONFIG_FILE "/etc/logid.cfg" +#ifndef LOGIOPS_VERSION +#define LOGIOPS_VERSION "null" +#warning Version is undefined! +#endif + using namespace logid; -LogLevel logid::global_verbosity = INFO; -Configuration* logid::global_config; -EvdevDevice* logid::global_evdev; -DeviceFinder* logid::finder; +struct CmdlineOptions +{ + std::string config_file = DEFAULT_CONFIG_FILE; +}; + +LogLevel logid::global_loglevel = INFO; +std::shared_ptr logid::global_config; +std::unique_ptr logid::device_manager; +std::unique_ptr logid::virtual_input; +std::shared_ptr logid::global_workqueue; + +bool logid::kill_logid = false; +std::mutex logid::device_manager_reload; enum class Option { None, Verbose, Config, - Help + Help, + Version }; -int main(int argc, char** argv) +/* +void logid::reload() { - std::string config_file = DEFAULT_CONFIG_FILE; - // Read command line options - for(int i = 1; i < argc; i++) - { + log_printf(INFO, "Reloading logid..."); + finder_reloading.lock(); + finder->stop(); + Configuration* old_config = global_config; + global_config = new Configuration(config_file.c_str()); + delete(old_config); + delete(finder); + finder = new DeviceMonitor(); + finder_reloading.unlock(); +} + */ + +void readCliOptions(const int argc, char** argv, CmdlineOptions& options) +{ + for(int i = 1; i < argc; i++) { Option option = Option::None; - if(argv[i][0] == '-') // This argument is an option - { - switch(argv[i][1]) // Set option - { - case '-': // Full option name - { - std::string op_str = argv[i]; - if (op_str == "--verbose") option = Option::Verbose; - if (op_str == "--config") option = Option::Config; - if (op_str == "--help") option = Option::Help; - break; - } - case 'v': // Verbosity - option = Option::Verbose; - break; - case 'c': // Config file path - option = Option::Config; - break; - case 'h': // Help - option = Option::Help; - break; - default: - log_printf(WARN, "%s is not a valid option, ignoring.", argv[1]); + if(argv[i][0] == '-') { + // This argument is an option + switch(argv[i][1]) { + case '-': { + // Full option name + std::string op_str = argv[i]; + if (op_str == "--verbose") option = Option::Verbose; + if (op_str == "--config") option = Option::Config; + if (op_str == "--help") option = Option::Help; + if (op_str == "--version") option = Option::Version; + break; } - switch(option) - { - case Option::Verbose: - { - if (++i >= argc) - { - global_verbosity = DEBUG; // Assume debug verbosity - break; - } - std::string loglevel = argv[i]; - try { global_verbosity = stringToLogLevel(argv[i]); } - catch (std::invalid_argument &e) - { - if (argv[i][0] == '-') - { - global_verbosity = DEBUG; // Assume debug verbosity - i--; // Go back to last argument to continue loop. - } - else - { - log_printf(WARN, e.what()); - printf("Valid verbosity levels are: Debug, Info, Warn/Warning, or Error.\n"); - return EXIT_FAILURE; - } - } + case 'v': // Verbosity + option = Option::Verbose; + break; + case 'V': //Version + option = Option::Version; + break; + case 'c': // Config file path + option = Option::Config; + break; + case 'h': // Help + option = Option::Help; + break; + default: + logPrintf(WARN, "%s is not a valid option, ignoring.", + argv[i]); + } + + switch(option) { + case Option::Verbose: { + if (++i >= argc) { + global_loglevel = DEBUG; // Assume debug verbosity break; } - case Option::Config: - { - if (++i >= argc) - { - log_printf(ERROR, "Config file is not specified."); - return EXIT_FAILURE; + std::string loglevel = argv[i]; + try { + global_loglevel = toLogLevel(argv[i]); + } catch (std::invalid_argument &e) { + if (argv[i][0] == '-') { + global_loglevel = DEBUG; // Assume debug verbosity + i--; // Go back to last argument to continue loop. + } else { + logPrintf(WARN, e.what()); + printf("Valid verbosity levels are: Debug, Info, " + "Warn/Warning, or Error.\n"); + exit(EXIT_FAILURE); } - config_file = argv[i]; - break; } - case Option::Help: - printf(R"(Usage: %s [options] + break; + } + case Option::Config: { + if (++i >= argc) { + logPrintf(ERROR, "Config file is not specified."); + exit(EXIT_FAILURE); + } + options.config_file = argv[i]; + break; + } + case Option::Help: + printf(R"(logid version %s +Usage: %s [options] Possible options are: -v,--verbose [level] Set log level to debug/info/warn/error (leave blank for debug) + -V,--version Print version number -c,--config [file path] Change config file from default at %s -h,--help Print this message. -)", argv[0], DEFAULT_CONFIG_FILE); - - return EXIT_SUCCESS; - case Option::None: - break; +)", LOGIOPS_VERSION, argv[0], DEFAULT_CONFIG_FILE); + exit(EXIT_SUCCESS); + case Option::Version: + printf("%s\n", LOGIOPS_VERSION); + exit(EXIT_SUCCESS); + case Option::None: + break; } } } +} + +int main(int argc, char** argv) +{ + CmdlineOptions options{}; + readCliOptions(argc, argv, options); // Read config - try { global_config = new Configuration(config_file.c_str()); } - catch (std::exception &e) { global_config = new Configuration(); } + try { + global_config = std::make_shared(options.config_file); + } + catch (std::exception &e) { + global_config = std::make_shared(); + } + global_workqueue = std::make_shared( + global_config->workerCount()); - //Create an evdev device called 'logid' - try { global_evdev = new EvdevDevice(evdev_name); } - catch(std::system_error& e) - { - log_printf(ERROR, "Could not create evdev device: %s", e.what()); + //Create a virtual input device + try { + virtual_input = std::make_unique(LOGID_VIRTUAL_INPUT_NAME); + } catch(std::system_error& e) { + logPrintf(ERROR, "Could not create input device: %s", e.what()); return EXIT_FAILURE; } // Scan devices, create listeners, handlers, etc. - finder = new DeviceFinder(); - finder->run(); + device_manager = std::make_unique(); + + while(!kill_logid) { + device_manager_reload.lock(); + device_manager_reload.unlock(); + device_manager->run(); + } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/src/logid/logid.h b/src/logid/logid.h new file mode 100644 index 0000000..850721f --- /dev/null +++ b/src/logid/logid.h @@ -0,0 +1,32 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef LOGID_LOGID_H +#define LOGID_LOGID_H + +#include + +namespace logid +{ + // void reload(); + + extern bool kill_logid; + extern std::mutex device_manager_reload; +} + +#endif //LOGID_LOGID_H \ No newline at end of file diff --git a/src/logid/util.cpp b/src/logid/util.cpp deleted file mode 100644 index 32d9aca..0000000 --- a/src/logid/util.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "util.h" - -using namespace logid; - -void logid::log_printf(LogLevel level, const char* format, ...) -{ - if(global_verbosity > level) return; - - va_list vargs; - va_start(vargs, format); - - FILE* stream = stdout; - if(level == ERROR || level == WARN) stream = stderr; - - fprintf(stream, "[%s] ", level_prefix(level)); - vfprintf(stream, format, vargs); - fprintf(stream, "\n"); -} - -const char* logid::level_prefix(LogLevel level) -{ - if(level == DEBUG) return "DEBUG"; - if(level == INFO) return "INFO" ; - if(level == WARN) return "WARN"; - if(level == ERROR) return "ERROR"; - - return "DEBUG"; -} - -Direction logid::getDirection(int x, int y) -{ - if(x == 0 && y == 0) return Direction::None; - - double angle; - - if(x == 0 && y > 0) angle = 90; // Y+ - else if(x == 0 && y < 0) angle = 270; // Y- - else if(x > 0 && y == 0) angle = 0; // X+ - else if(x < 0 && y == 0) angle = 180; // X+ - else - { - angle = fabs(atan((double)y/(double)x) * 180.0 / M_PI); - - if(x < 0 && y > 0) angle = 180.0 - angle; //Q2 - else if(x < 0 && y < 0) angle += 180; // Q3 - else if(x > 0 && y < 0) angle = 360.0 - angle; // Q4 - } - - if(315 < angle || angle <= 45) return Direction::Right; - else if(45 < angle && angle <= 135) return Direction::Down; - else if(135 < angle && angle <= 225) return Direction::Left; - else if(225 < angle && angle <= 315) return Direction::Up; - - return Direction::None; -} - -Direction logid::stringToDirection(std::string s) -{ - const char* original_str = s.c_str(); - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - - if(s == "none") return Direction::None; - if(s == "up") return Direction::Up; - if(s == "down") return Direction::Down; - if(s == "left") return Direction::Left; - if(s == "right") return Direction::Right; - - s = original_str; - - throw std::invalid_argument(s + " is an invalid direction."); -} - -GestureMode logid::stringToGestureMode(std::string s) -{ - const char* original_str = s.c_str(); - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - - if(s == "nopress") return GestureMode::NoPress; - if(s == "onrelease") return GestureMode::OnRelease; - if(s == "onfewpixels") return GestureMode::OnFewPixels; - if(s == "axis") return GestureMode::Axis; - - s = original_str; - - log_printf(INFO, "%s is an invalid gesture mode. Defaulting to OnRelease", original_str); - - - return GestureMode::OnRelease; -} - -Action logid::stringToAction(std::string s) -{ - std::string original_str = s; - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - - if(s == "none") return Action::None; - if(s == "keypress") return Action::Keypress; - if(s == "gestures") return Action::Gestures; - if(s == "togglesmartshift") return Action::ToggleSmartshift; - if(s == "togglehiresscroll") return Action::ToggleHiresScroll; - if(s == "cycledpi") return Action::CycleDPI; - if(s == "changedpi") return Action::ChangeDPI; - - throw std::invalid_argument(original_str + " is an invalid action."); -} - -LogLevel logid::stringToLogLevel(std::string s) -{ - std::string original_str = s; - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - - if(s == "debug") return DEBUG; - if(s == "info") return INFO; - if(s == "warn" || s == "warning") return WARN; - if(s == "error") return ERROR; - - throw std::invalid_argument(original_str + " is an invalid log level."); -} \ No newline at end of file diff --git a/src/logid/util.h b/src/logid/util.h deleted file mode 100644 index 7f41cb7..0000000 --- a/src/logid/util.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef LOGID_UTIL_H -#define LOGID_UTIL_H - -#include "Actions.h" - -namespace logid -{ - enum LogLevel - { - DEBUG, - INFO, - WARN, - ERROR - }; - - extern LogLevel global_verbosity; - - void log_printf(LogLevel level, const char* format, ...); - - const char* level_prefix(LogLevel level); - - Direction getDirection(int x, int y); - Direction stringToDirection(std::string s); - GestureMode stringToGestureMode(std::string s); - Action stringToAction(std::string s); - LogLevel stringToLogLevel(std::string s); -} - -#endif //LOGID_UTIL_H \ No newline at end of file diff --git a/src/logid/util/ExceptionHandler.cpp b/src/logid/util/ExceptionHandler.cpp new file mode 100644 index 0000000..48c723f --- /dev/null +++ b/src/logid/util/ExceptionHandler.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "log.h" +#include "ExceptionHandler.h" +#include "../backend/hidpp10/Error.h" +#include "../backend/hidpp20/Error.h" + +using namespace logid; + +void ExceptionHandler::Default(std::exception& error) +{ + try { + throw error; + } catch(backend::hidpp10::Error& e) { + logPrintf(WARN, "HID++ 1.0 error ignored on detached thread/task: %s", + error.what()); + } catch(backend::hidpp20::Error& e) { + logPrintf(WARN, "HID++ 2.0 error ignored on detached thread/task: %s", + error.what()); + } catch(std::system_error& e) { + logPrintf(WARN, "System error ignored on detached thread/task: %s", + error.what()); + } catch(std::exception& e) { + logPrintf(WARN, "Error ignored on detached thread/task: %s", + error.what()); + } +} \ No newline at end of file diff --git a/src/logid/util/ExceptionHandler.h b/src/logid/util/ExceptionHandler.h new file mode 100644 index 0000000..daf9bea --- /dev/null +++ b/src/logid/util/ExceptionHandler.h @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_EXCEPTIONHANDLER_H +#define LOGID_EXCEPTIONHANDLER_H + +#include + +namespace logid { +namespace ExceptionHandler +{ + void Default(std::exception& e); +}} + +#endif //LOGID_EXCEPTIONHANDLER_H diff --git a/src/logid/util/log.cpp b/src/logid/util/log.cpp new file mode 100644 index 0000000..af7bfe4 --- /dev/null +++ b/src/logid/util/log.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include "log.h" + +using namespace logid; + +void logid::logPrintf(LogLevel level, const char* format, ...) +{ + if(global_loglevel > level) return; + + va_list vargs; + va_start(vargs, format); + + FILE* stream = stdout; + if(level == ERROR || level == WARN) + stream = stderr; + + fprintf(stream, "[%s] ", levelPrefix(level)); + vfprintf(stream, format, vargs); + fprintf(stream, "\n"); +} + +const char* logid::levelPrefix(LogLevel level) +{ + switch(level) { + case RAWREPORT: + return "RAWREPORT"; + case DEBUG: + return "DEBUG"; + case INFO: + return "INFO"; + case WARN: + return "WARN"; + case ERROR: + return "ERROR"; + default: + return "UNKNOWN"; + } +} + + +LogLevel logid::toLogLevel(std::string s) +{ + std::string original_str = s; + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + + if(s == "rawreport") + return RAWREPORT; + if(s == "debug") + return DEBUG; + if(s == "info") + return INFO; + if(s == "warn" || s == "warning") + return WARN; + if(s == "error") + return ERROR; + + throw std::invalid_argument(original_str + " is an invalid log level."); +} \ No newline at end of file diff --git a/src/logid/util/log.h b/src/logid/util/log.h new file mode 100644 index 0000000..fad0b66 --- /dev/null +++ b/src/logid/util/log.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_LOG_H +#define LOGID_LOG_H + +#include + +namespace logid +{ + enum LogLevel + { + RAWREPORT, + DEBUG, + INFO, + WARN, + ERROR + }; + + extern LogLevel global_loglevel; + + void logPrintf(LogLevel level, const char *format, ...); + const char *levelPrefix(LogLevel level); + LogLevel toLogLevel(std::string s); +} + +#endif //LOGID_LOG_H diff --git a/src/logid/util/mutex_queue.h b/src/logid/util/mutex_queue.h new file mode 100644 index 0000000..9efc927 --- /dev/null +++ b/src/logid/util/mutex_queue.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MUTEX_QUEUE_H +#define MUTEX_QUEUE_H + +#include +#include + +template +class mutex_queue +{ +public: + mutex_queue() = default; + bool empty() + { + std::lock_guard lock(_mutex); + return _queue.empty(); + } + data& front() + { + std::lock_guard lock(_mutex); + return _queue.front(); + } + void push(const data& _data) + { + std::lock_guard lock(_mutex); + _queue.push(_data); + } + void pop() + { + std::lock_guard lock(_mutex); + _queue.pop(); + } +private: + std::queue _queue; + std::mutex _mutex; +}; + +#endif //MUTEX_QUEUE_H \ No newline at end of file diff --git a/src/logid/util/task.cpp b/src/logid/util/task.cpp new file mode 100644 index 0000000..29a4987 --- /dev/null +++ b/src/logid/util/task.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "task.h" +#include "workqueue.h" + +using namespace logid; + +task::task(const std::function& function, + const std::function& exception_handler) : + _function (std::make_shared>(function)), + _exception_handler (std::make_shared> + (exception_handler)), _status (Waiting), + _task_pkg ([this](){ + try { + (*_function)(); + } catch(std::exception& e) { + (*_exception_handler)(e); + } + }), _future (_task_pkg.get_future()) +{ +} + +void task::run() +{ + _status = Running; + _status_cv.notify_all(); + _task_pkg(); + _status = Completed; + _status_cv.notify_all(); +} + +task::Status task::getStatus() +{ + return _status; +} + +void task::wait() +{ + if(_future.valid()) + _future.wait(); + else { + std::mutex wait_start; + std::unique_lock lock(wait_start); + _status_cv.wait(lock, [this](){ return _status == Completed; }); + } +} + +void task::waitStart() +{ + std::mutex wait_start; + std::unique_lock lock(wait_start); + _status_cv.wait(lock, [this](){ return _status != Waiting; }); +} + +std::future_status task::waitFor(std::chrono::milliseconds ms) +{ + return _future.wait_for(ms); +} + +void task::spawn(const std::function& function, + const std::function& exception_handler) +{ + auto t = std::make_shared(function, exception_handler); + global_workqueue->queue(t); +} \ No newline at end of file diff --git a/src/logid/util/task.h b/src/logid/util/task.h new file mode 100644 index 0000000..c988cc9 --- /dev/null +++ b/src/logid/util/task.h @@ -0,0 +1,69 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_TASK_H +#define LOGID_TASK_H + +#include +#include +#include +#include "ExceptionHandler.h" + +namespace logid +{ + class task + { + public: + enum Status + { + Waiting, + Running, + Completed + }; + + explicit task(const std::function& function, + const std::function& + exception_handler={[](std::exception& e) + {ExceptionHandler::Default(e);}}); + + Status getStatus(); + + void run(); // Runs synchronously + void wait(); + void waitStart(); + std::future_status waitFor(std::chrono::milliseconds ms); + + /* This function spawns a new task into the least used worker queue + * and forgets about it. + */ + static void spawn(const std::function& function, + const std::function& + exception_handler={[](std::exception& e) + {ExceptionHandler::Default(e);}}); + + private: + std::shared_ptr> _function; + std::shared_ptr> + _exception_handler; + std::atomic _status; + std::condition_variable _status_cv; + std::packaged_task _task_pkg; + std::future _future; + }; +} + +#endif //LOGID_TASK_H diff --git a/src/logid/util/thread.cpp b/src/logid/util/thread.cpp new file mode 100644 index 0000000..be496dc --- /dev/null +++ b/src/logid/util/thread.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "thread.h" + +using namespace logid; + +thread::thread(const std::function& function, + const std::function& exception_handler) + : _function (std::make_shared>(function)), + _exception_handler (std::make_shared> (exception_handler)) +{ +} + +thread::~thread() +{ + if(_thread) + if(_thread->joinable()) + _thread->detach(); +} + +void thread::spawn(const std::function& function, + const std::function& exception_handler) +{ + std::thread([function, exception_handler](){ + thread t(function, exception_handler); + t.runSync(); + }).detach(); +} + +void thread::run() +{ + _thread = std::make_shared( + [f=this->_function,eh=this->_exception_handler]() { + try { + (*f)(); + } catch (std::exception& e) { + (*eh)(e); + } + }); +} + +void thread::wait() +{ + if(_thread) + if(_thread->joinable()) + _thread->join(); +} + +void thread::runSync() +{ + try { + (*_function)(); + } catch(std::exception& e) { + (*_exception_handler)(e); + } +} \ No newline at end of file diff --git a/src/logid/util/thread.h b/src/logid/util/thread.h new file mode 100644 index 0000000..311a1f6 --- /dev/null +++ b/src/logid/util/thread.h @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_THREAD_H +#define LOGID_THREAD_H + +#include +#include +#include +#include "ExceptionHandler.h" + +namespace logid +{ + class thread + { + public: + explicit thread(const std::function& function, + const std::function& + exception_handler={[](std::exception& e) + {ExceptionHandler::Default(e);}}); + + ~thread(); + + /* This function spawns a new thread and forgets about it, + * safe equivalent to std::thread{...}.detach() + */ + static void spawn(const std::function& function, + const std::function& + exception_handler={[](std::exception& e) + {ExceptionHandler::Default(e);}}); + + void run(); + void wait(); + void runSync(); + private: + std::shared_ptr> _function; + std::shared_ptr> + _exception_handler; + std::shared_ptr _thread = nullptr; + }; +} + +#endif //LOGID_THREAD_H diff --git a/src/logid/util/worker_thread.cpp b/src/logid/util/worker_thread.cpp new file mode 100644 index 0000000..116e991 --- /dev/null +++ b/src/logid/util/worker_thread.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "worker_thread.h" +#include "log.h" +#include "workqueue.h" + +using namespace logid; + +worker_thread::worker_thread(workqueue* parent, std::size_t worker_number) : +_parent (parent), _worker_number (worker_number), _continue_run (false), +_thread (std::make_unique ([this](){ + _run(); }, [this](std::exception& e){ _exception_handler(e); })) +{ + _thread->run(); +} + +worker_thread::~worker_thread() +{ + _continue_run = false; + _queue_cv.notify_all(); + // Block until task is complete + std::unique_lock lock(_busy); + + while(!_queue.empty()) { + _parent->queue(_queue.front()); + _queue.pop(); + } +} + +void worker_thread::queue(std::shared_ptr t) +{ + _queue.push(t); + _queue_cv.notify_all(); +} + +bool worker_thread::busy() +{ + bool not_busy = _busy.try_lock(); + + if(not_busy) + _busy.unlock(); + + return !not_busy; +} + +void worker_thread::_run() +{ + std::unique_lock lock(_run_lock); + _continue_run = true; + while(_continue_run) { + _parent->busyUpdate(); + _queue_cv.wait(lock, [this]{ return !_queue.empty() || + !_continue_run; }); + if(!_continue_run) + return; + std::unique_lock busy_lock(_busy); + while(!_queue.empty()) { + _queue.front()->run(); + _queue.pop(); + } + } +} + +void worker_thread::_exception_handler(std::exception &e) +{ + logPrintf(WARN, "Exception caught on worker thread %d, restarting: %s", + _worker_number, e.what()); + // This action destroys the logid::thread, std::thread should detach safely. + _thread = std::make_unique([this](){ _run(); }, + [this](std::exception& e) { _exception_handler(e); }); + _thread->run(); +} \ No newline at end of file diff --git a/src/logid/util/worker_thread.h b/src/logid/util/worker_thread.h new file mode 100644 index 0000000..0c252e1 --- /dev/null +++ b/src/logid/util/worker_thread.h @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_WORKER_THREAD_H +#define LOGID_WORKER_THREAD_H + +#include "mutex_queue.h" +#include "task.h" +#include "thread.h" + +namespace logid +{ + class workqueue; + + class worker_thread + { + public: + worker_thread(workqueue* parent, std::size_t worker_number); + ~worker_thread(); + + void queue(std::shared_ptr t); + + bool busy(); + private: + void _run(); + void _exception_handler(std::exception& e); + + workqueue* _parent; + std::size_t _worker_number; + + std::mutex _run_lock; + std::atomic _continue_run; + std::condition_variable _queue_cv; + + std::unique_ptr _thread; + std::mutex _busy; + + mutex_queue> _queue; + }; +} + +#endif //LOGID_WORKER_THREAD_H diff --git a/src/logid/util/workqueue.cpp b/src/logid/util/workqueue.cpp new file mode 100644 index 0000000..2bf409d --- /dev/null +++ b/src/logid/util/workqueue.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "workqueue.h" +#include "log.h" + +using namespace logid; + +workqueue::workqueue(std::size_t thread_count) : _manager_thread ( + std::make_unique( + [this](){ _run(); } + , [this](std::exception& e){ _exception_handler(e); } + )), _continue_run (false), _worker_count (thread_count) +{ + _workers.reserve(_worker_count); + for(std::size_t i = 0; i < _worker_count; i++) + _workers.push_back(std::make_unique(this, i)); + _manager_thread->run(); +} + +workqueue::~workqueue() +{ + stop(); + + while(_workers.empty()) + _workers.pop_back(); + + // Queue should have been empty before, but just confirm here. + while(!_queue.empty()) { + thread::spawn([t=_queue.front()](){ t->run(); }); + _queue.pop(); + } +} + +void workqueue::queue(std::shared_ptr t) +{ + assert(t != nullptr); + _queue.push(t); + _queue_cv.notify_all(); +} + +void workqueue::busyUpdate() +{ + _busy_cv.notify_all(); +} + +void workqueue::stop() +{ + _continue_run = false; + std::unique_lock lock(_run_lock); +} + +std::size_t workqueue::threadCount() const +{ + return _workers.size(); +} + +void workqueue::_run() +{ + using namespace std::chrono_literals; + + std::unique_lock lock(_run_lock); + _continue_run = true; + while(_continue_run) { + _queue_cv.wait(lock, [this]{ return !(_queue.empty()); }); + while(!_queue.empty()) { + if(_workers.empty()) { + if(_worker_count) + logPrintf(DEBUG, "No workers were found, running task in" + " a new thread."); + thread::spawn([t=_queue.front()](){ t->run(); }); + _queue.pop(); + continue; + } + + auto worker = _workers.begin(); + for(; worker != _workers.end(); worker++) { + if(!(*worker)->busy()) + break; + } + if(worker != _workers.end()) + (*worker)->queue(_queue.front()); + else { + _busy_cv.wait_for(lock, 500ms, [this, &worker]{ + for(worker = _workers.begin(); worker != _workers.end(); + worker++) { + if (!(*worker)->busy()) { + return true; + } + } + return false; + }); + + if(worker != _workers.end()) + (*worker)->queue(_queue.front()); + else{ + // Workers busy, launch in new thread + logPrintf(DEBUG, "All workers were busy for 500ms, " + "running task in new thread."); + thread::spawn([t = _queue.front()]() { t->run(); }); + } + } + _queue.pop(); + } + } +} + +void workqueue::_exception_handler(std::exception &e) +{ + logPrintf(WARN, "Exception caught on workqueue manager thread, " + "restarting: %s" , e.what()); + // This action destroys the logid::thread, std::thread should detach safely. + _manager_thread = std::make_unique([this](){ _run(); }, + [this](std::exception& e) { _exception_handler(e); }); + _manager_thread->run(); +} \ No newline at end of file diff --git a/src/logid/util/workqueue.h b/src/logid/util/workqueue.h new file mode 100644 index 0000000..301b60b --- /dev/null +++ b/src/logid/util/workqueue.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019-2020 PixlOne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef LOGID_WORKQUEUE_H +#define LOGID_WORKQUEUE_H + +#include "worker_thread.h" +#include "thread.h" + +namespace logid +{ + class workqueue + { + public: + explicit workqueue(std::size_t thread_count); + ~workqueue(); + + void queue(std::shared_ptr t); + + void busyUpdate(); + + void stop(); + + std::size_t threadCount() const; + private: + void _run(); + + void _exception_handler(std::exception& e); + std::unique_ptr _manager_thread; + + mutex_queue> _queue; + std::condition_variable _queue_cv; + std::condition_variable _busy_cv; + std::mutex _run_lock; + std::atomic _continue_run; + + std::vector> _workers; + std::size_t _worker_count; + }; + + extern std::shared_ptr global_workqueue; +} + +#endif //LOGID_WORKQUEUE_H