Merge pull request #99 from PixlOne/refactor

Refactor LogiOps
This commit is contained in:
pixl 2020-07-15 09:29:13 -04:00 committed by GitHub
commit c83739beb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
124 changed files with 9801 additions and 2195 deletions

4
.gitmodules vendored
View File

@ -1,4 +0,0 @@
[submodule "src/logid/hidpp"]
path = src/logid/hidpp
url = https://github.com/PixlOne/hidpp.git
branch = master

View File

@ -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)

View File

@ -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

View File

@ -1,238 +0,0 @@
#include <unistd.h>
#include <future>
#include <hidpp20/Error.h>
#include <hidpp/SimpleDispatcher.h>
#include <hidpp20/IAdjustableDPI.h>
#include <hidpp20/ISmartShift.h>
#include <hidpp20/IHiresScroll.h>
#include <cmath>
#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());
}
}

View File

@ -1,146 +0,0 @@
#ifndef LOGID_ACTIONS_H
#define LOGID_ACTIONS_H
#include "Device.h"
#include <map>
#include <vector>
#include <hidpp20/IReprogControls.h>
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<unsigned int> k) : ButtonAction(Action::Keypress), keys (std::move(k)) {};
virtual KeyAction* copy(Device* dev);
virtual void press();
virtual void release();
private:
std::vector<unsigned int> 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<Direction, Gesture*> g) : ButtonAction(Action::Gestures), gestures (std::move(g)) {};
std::map<Direction, Gesture*> 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<int> d) : ButtonAction(Action::CycleDPI), dpis (d) {};
virtual CycleDPIAction* copy(Device* dev);
virtual void press();
virtual void release() {}
private:
const std::vector<int> 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

View File

@ -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)

View File

@ -1,518 +1,178 @@
#include <cstdint>
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <utility>
#include <vector>
#include <map>
#include <linux/input-event-codes.h>
#include <libevdev/libevdev.h>
#include <algorithm>
#include <cstring>
#include <string>
#include <hidpp20/IHiresScroll.h>
#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())
break;
if(_blacklist.isList())
continue;
}
blacklist.push_back((int)_blacklist[i]);
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<milliseconds>(
duration<double, std::milli>(timeout));
else
{
log_printf(WARN, "Line %d: blacklist must be an array or list, "
"ignnoring.", _blacklist.getSourceLine());
}
}
catch(const SettingNotFoundException &e)
{
_io_timeout = milliseconds((int)timeout);
} else
logPrintf(WARN, "Line %d: io_timeout must be a number.",
timeout.getSourceLine());
} catch(const SettingNotFoundException& e) {
// Ignore
}
Setting* _devices;
try {
auto& devices = root["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];
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());
try {
if(!device.lookupValue("name", name)) {
logPrintf(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());
} catch(SettingNotFoundException &e) {
logPrintf(WARN, "Line %d: Missing name field, skipping device."
, device.getSourceLine());
continue;
}
devices.insert({name, new DeviceConfig(device)});
_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;
} 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]);
}
}
} catch(const SettingNotFoundException& e) {
// Ignore
}
}
}
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());
return _config.lookup(path);
}
try
std::string Configuration::getDevice(const std::string& name)
{
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());
auto it = _device_paths.find(name);
if(it == _device_paths.end())
throw DeviceNotFound(name);
else
return it->second;
}
try
bool Configuration::isIgnored(uint16_t pid) const
{
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());
return _ignore_list.find(pid) != _ignore_list.end();
}
try
Configuration::DeviceNotFound::DeviceNotFound(std::string name) :
_name (std::move(name))
{
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)
const char * Configuration::DeviceNotFound::what() const noexcept
{
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());
return _name.c_str();
}
try
int Configuration::workerCount() const
{
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());
return _worker_threads;
}
Setting* buttons;
try
std::chrono::milliseconds Configuration::ioTimeout() const
{
buttons = &root["buttons"];
}
catch(const SettingNotFoundException &e)
{
log_printf(WARN, "No button configuration found, reverting to null config.");
new std::map<uint16_t, ButtonAction*>();
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()); }
}
}
ButtonAction* logid::parse_action(Action type, const Setting* action_config, bool is_gesture)
{
if(type == Action::None) return new NoAction();
if(type == Action::Keypress)
{
std::vector<unsigned int> 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<Direction, Gesture*> 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<int> 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();
}
DeviceConfig::DeviceConfig(DeviceConfig* dc, Device* dev) : baseConfig (false)
{
dpi = dc->dpi;
smartshift = dc->smartshift;
hiresscroll = dc->hiresscroll;
for(auto it : dc->actions)
actions.insert( { it.first, it.second->copy(dev) } );
}
DeviceConfig::DeviceConfig()
{
dpi = nullptr;
hiresscroll = nullptr;
smartshift = nullptr;
actions = {};
}
DeviceConfig::~DeviceConfig()
{
for(auto it : this->actions)
delete(it.second);
return _io_timeout;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_CONFIGURATION_H
#define LOGID_CONFIGURATION_H
#include <map>
#include <libconfig.h++>
#include <hidpp20/ISmartShift.h>
#include "Actions.h"
#include <memory>
#include <chrono>
#include <set>
#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<uint16_t, ButtonAction*> actions;
const bool baseConfig = true;
};
class Configuration
{
public:
Configuration(const char* config_file);
Configuration() {}
std::map<std::string, DeviceConfig*> devices;
std::vector<uint16_t> 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:
libconfig::Config cfg;
std::string _name;
};
ButtonAction* parse_action(Action action, const libconfig::Setting* action_config, bool is_gesture=false);
std::chrono::milliseconds ioTimeout() const;
int workerCount() const;
private:
std::map<std::string, std::string> _device_paths;
std::set<uint16_t> _ignore_list;
std::chrono::milliseconds _io_timeout = LOGID_DEFAULT_IO_TIMEOUT;
int _worker_threads = LOGID_DEFAULT_WORKER_COUNT;
libconfig::Config _config;
};
extern Configuration* global_config;
extern std::shared_ptr<Configuration> global_config;
}
#endif //LOGID_CONFIGURATION_H

View File

@ -1,551 +1,139 @@
#include <cstdint>
#include <future>
#include <unistd.h>
#include <hidpp/SimpleDispatcher.h>
#include <hidpp20/IAdjustableDPI.h>
#include <hidpp20/IFeatureSet.h>
#include <hidpp20/Error.h>
#include <hidpp20/IReprogControls.h>
#include <hidpp20/IReset.h>
#include <hidpp20/ISmartShift.h>
#include <hidpp20/Device.h>
#include <hidpp10/Error.h>
#include <algorithm>
#include <cstring>
#include <utility>
#include <set>
#include <hidpp20/UnsupportedFeature.h>
#include <hidpp20/IHiresScroll.h>
/*
* Copyright 2019-2020 PixlOne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "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<backend::raw::RawDevice>& 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();
_init();
}
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
void Device::_init()
{
delete(config);
config = global_config->devices.find(name)->second;
logPrintf(INFO, "Device found: %s on %s:%d", name().c_str(),
hidpp20().devicePath().c_str(), _index);
_addFeature<features::DPI>("dpi");
_addFeature<features::SmartShift>("smartshift");
_addFeature<features::HiresScroll>("hiresscroll");
_addFeature<features::RemapButton>("remapbutton");
_makeResetMechanism();
reset();
for(auto& feature: _features) {
feature.second->configure();
feature.second->listen();
}
initialized = true;
return true;
_hidpp20.listen();
}
Device::~Device()
std::string Device::name()
{
if(!disconnected)
this->reset();
if(!config->baseConfig)
delete(this->config);
return _hidpp20.name();
}
void Device::configure()
uint16_t Device::pid()
{
if(config->baseConfig)
config = new DeviceConfig(config, this);
if(!configuring.try_lock())
{
log_printf(DEBUG, "%s %d: skip config task", path.c_str(), index);
return;
}
usleep(500000);
try
{
if(disconnected)
goto ret;
// Divert buttons
divert_buttons();
if(disconnected)
goto ret;
// Set DPI if it is configured
if(config->dpi != nullptr)
setDPI(*config->dpi);
if(disconnected)
goto ret;
// Set Smartshift if it is configured
if(config->smartshift != nullptr)
setSmartShift(*config->smartshift);
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());
return _hidpp20.pid();
}
ret:
configuring.unlock();
void Device::sleep()
{
logPrintf(INFO, "%s:%d fell asleep.", _path.c_str(), _index);
}
void Device::wakeup()
{
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
reset();
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());
}
}
void Device::divert_buttons()
{
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());
}
}
void Device::setSmartShift(HIDPP20::ISmartShift::SmartshiftStatus ops)
{
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());
}
}
void Device::setHiresScroll(uint8_t ops)
{
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());
}
}
void Device::setDPI(int dpi)
{
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<ReceiverHandler>(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<ButtonHandler>(this)); }
catch(HIDPP20::UnsupportedFeature &e) { }
if(index == HIDPP::DefaultDevice || index == HIDPP::CordedDevice)
{
try { listener->addEventHandler( std::make_unique<WirelessStatusHandler>(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<uint16_t>::iterator it;
std::vector<uint16_t> 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();
}
if(_reset_mechanism)
(*_reset_mechanism)();
else
{
if(!dev->initialized) return;
dev->disconnected = false;
dev->configure();
log_printf(INFO, "Connection established to %s", dev->name.c_str());
}
}
break;
}
default:
break;
}
logPrintf(DEBUG, "%s:%d tried to reset, but no reset mechanism was "
"available.", _path.c_str(), _index);
}
void WirelessStatusHandler::handleEvent(const HIDPP::Report &event)
DeviceConfig& Device::config()
{
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;
}
}
return _config;
}
void EventListener::removeEventHandlers ()
hidpp20::Device& Device::hidpp20()
{
for (const auto &p: iterators)
dispatcher->unregisterEventHandler(p.second);
handlers.clear();
iterators.clear();
return _hidpp20;
}
EventListener::~EventListener()
void Device::_makeResetMechanism()
{
removeEventHandlers();
}
void EventListener::addEventHandler(std::unique_ptr<EventHandler> &&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;
try {
hidpp20::Reset reset(&_hidpp20);
_reset_mechanism = std::make_unique<std::function<void()>>(
[dev=&this->_hidpp20]{
hidpp20::Reset reset(dev);
reset.reset(reset.getProfile());
});
} catch(hidpp20::UnsupportedFeature& e) {
// Reset unsupported, ignore.
}
}
void SimpleListener::start()
DeviceConfig::DeviceConfig(const std::shared_ptr<Configuration>& config, Device*
device) : _device (device), _config (config)
{
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;
try {
_root_setting = config->getDevice(device->name());
} catch(Configuration::DeviceNotFound& e) {
logPrintf(INFO, "Device %s not configured, using default config.",
device->name().c_str());
}
}
std::map<uint16_t, uint8_t> Device::getFeatures()
libconfig::Setting& DeviceConfig::getSetting(const std::string& path)
{
std::map<uint16_t, uint8_t> _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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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 <map>
#include <memory>
#include <atomic>
#include <hidpp/Dispatcher.h>
#include <hidpp/SimpleDispatcher.h>
#include <hidpp10/IReceiver.h>
#include <hidpp20/IWirelessDeviceStatus.h>
#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<Configuration>& config, Device*
device);
libconfig::Setting& getSetting(const std::string& path);
private:
Device* _device;
std::string _root_setting;
std::shared_ptr<Configuration> _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<backend::raw::RawDevice>& 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<uint16_t, uint8_t> getFeatures();
std::map<uint16_t, uint8_t> 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<uint8_t> 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;
template<typename T>
std::shared_ptr<T> getFeature(std::string name) {
auto it = _features.find(name);
if(it == _features.end())
return nullptr;
try {
return std::dynamic_pointer_cast<T>(it->second);
} catch(std::bad_cast& e) {
logPrintf(ERROR, "bad_cast while getting device feature %s: %s",
name.c_str(), e.what());
return nullptr;
}
void handleEvent (const HIDPP::Report &event);
protected:
Device* dev;
HIDPP20::IReprogControls _irc;
std::vector<uint16_t> states;
std::vector<uint16_t> 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<uint8_t> 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<uint8_t, std::unique_ptr<EventHandler>> handlers;
std::map<uint8_t, HIDPP::Dispatcher::listener_iterator> iterators;
public:
EventListener (HIDPP::Dispatcher *dispatcher, HIDPP::DeviceIndex index): dispatcher (dispatcher), index (index) {}
virtual void removeEventHandlers ();
virtual ~EventListener();
virtual void addEventHandler (std::unique_ptr<EventHandler> &&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)
{
}
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<typename T>
void _addFeature(std::string name)
{
try {
_features.emplace(name, std::make_shared<T>(this));
} catch (backend::hidpp20::UnsupportedFeature& e) {
}
}
backend::hidpp20::Device _hidpp20;
std::string _path;
backend::hidpp::DeviceIndex _index;
std::map<std::string, std::shared_ptr<features::DeviceFeature>>
_features;
DeviceConfig _config;
void _makeResetMechanism();
std::unique_ptr<std::function<void()>> _reset_mechanism;
};
}
#endif //LOGID_DEVICE_H

View File

@ -1,212 +0,0 @@
#include <hid/DeviceMonitor.h>
#include <hidpp/SimpleDispatcher.h>
#include <hidpp/Device.h>
#include <hidpp10/Error.h>
#include <hidpp10/IReceiver.h>
#include <hidpp20/Error.h>
#include <cstring>
#include <unistd.h>
#include <thread>
#include <fstream>
#include <sstream>
#include <chrono>
#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<HIDPP::DeviceIndex, ConnectedDevice>()).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<HIDPP::DeviceIndex, ConnectedDevice>()).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));
}

View File

@ -1,48 +0,0 @@
#ifndef LOGID_DEVICEFINDER_H
#define LOGID_DEVICEFINDER_H
#include <hid/DeviceMonitor.h>
#include <hidpp/SimpleDispatcher.h>
#include <hidpp10/Device.h>
#include <hidpp10/IReceiver.h>
#include <hidpp20/IReprogControls.h>
#include <map>
#include <thread>
#include <mutex>
#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<std::string, std::map<HIDPP::DeviceIndex, ConnectedDevice>> devices;
};
extern DeviceFinder* finder;
}
#endif //LOGID_DEVICEFINDER_H

110
src/logid/DeviceManager.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <thread>
#include <sstream>
#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<Receiver>(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<Device>(path, hidpp::DefaultDevice);
_devices.emplace(path, device);
} else {
try {
auto device = std::make_shared<Device>(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());
}
}
}

50
src/logid/DeviceManager.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_DEVICEMANAGER_H
#define LOGID_DEVICEMANAGER_H
#include <map>
#include <thread>
#include <mutex>
#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<std::string, std::shared_ptr<Device>> _devices;
std::map<std::string, std::shared_ptr<Receiver>> _receivers;
};
extern std::unique_ptr<DeviceManager> device_manager;
}
#endif //LOGID_DEVICEMANAGER_H

View File

@ -1,43 +0,0 @@
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#include <system_error>
#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);
}

View File

@ -1,27 +0,0 @@
#ifndef LOGID_EVDEVDEVICE_H
#define LOGID_EVDEVDEVICE_H
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
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

106
src/logid/InputDevice.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <system_error>
#include "InputDevice.h"
extern "C"
{
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
}
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);
}

64
src/logid/InputDevice.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_INPUTDEVICE_H
#define LOGID_INPUTDEVICE_H
#include <memory>
extern "C"
{
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
}
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<InputDevice> virtual_input;
}
#endif //LOGID_INPUTDEVICE_H

90
src/logid/Receiver.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<std::mutex> 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> device = std::make_shared<Device>(
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<std::mutex> lock(_devices_change);
_devices.erase(index);
}

43
src/logid/Receiver.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_RECEIVER_H
#define LOGID_RECEIVER_H
#include <string>
#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<backend::hidpp::DeviceIndex, std::shared_ptr<Device>> _devices;
std::string _path;
};
}
#endif //LOGID_RECEIVER_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <algorithm>
#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> 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<KeypressAction>(device, setting);
else if(type == "togglesmartshift")
return std::make_shared<ToggleSmartShift>(device);
else if(type == "togglehiresscroll")
return std::make_shared<ToggleHiresScroll>(device);
else if(type == "gestures")
return std::make_shared<GestureAction>(device, setting);
else if(type == "cycledpi")
return std::make_shared<CycleDPI>(device, setting);
else if(type == "changedpi")
return std::make_shared<ChangeDPI>(device, setting);
else if(type == "none")
return std::make_shared<NullAction>(device);
else
throw InvalidAction(type);
} catch(libconfig::SettingNotFoundException& e) {
logPrintf(WARN, "Line %d: Action type is missing, ignoring.",
setting.getSourceLine());
throw InvalidAction();
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_ACTION_H
#define LOGID_ACTION_H
#include <atomic>
#include <libconfig.h++>
#include <memory>
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<Action> 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<bool> _pressed;
};
}}
#endif //LOGID_ACTION_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<features::DPI>("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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_ACTION_CHANGEDPI_H
#define LOGID_ACTION_CHANGEDPI_H
#include <libconfig.h++>
#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<features::DPI> _dpi;
};
}}
#endif //LOGID_ACTION_CHANGEDPI_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<features::DPI>("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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_ACTION_CYCLEDPI_H
#define LOGID_ACTION_CYCLEDPI_H
#include <libconfig.h++>
#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<uint16_t> _dpis;
uint8_t _sensor;
};
protected:
Config _config;
std::shared_ptr<features::DPI> _dpi;
};
}}
#endif //LOGID_ACTION_CYCLEDPI_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <algorithm>
#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::Direction, std::shared_ptr<Gesture>>&
GestureAction::Config::gestures()
{
return _gestures;
}
std::shared_ptr<Action> GestureAction::Config::noneAction()
{
return _none_action;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_ACTION_GESTUREACTION_H
#define LOGID_ACTION_GESTUREACTION_H
#include <map>
#include <libconfig.h++>
#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<Direction, std::shared_ptr<Gesture>>& gestures();
std::shared_ptr<Action> noneAction();
protected:
std::map<Direction, std::shared_ptr<Gesture>> _gestures;
std::shared_ptr<Action> _none_action;
};
protected:
int16_t _x, _y;
Config _config;
};
}}
#endif //LOGID_ACTION_GESTUREACTION_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<uint>& KeypressAction::Config::keys()
{
return _keys;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_ACTION_KEYPRESS_H
#define LOGID_ACTION_KEYPRESS_H
#include <vector>
#include <libconfig.h++>
#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<uint>& keys();
protected:
std::vector<uint> _keys;
};
protected:
Config _config;
};
}}
#endif //LOGID_ACTION_KEYPRESS_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<features::HiresScroll>("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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<features::HiresScroll> _hires_scroll;
};
}}
#endif //LOGID_ACTION_TOGGLEHIRESSCROLL_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<features::SmartShift>("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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_ACTION_TOGGLESMARTSHIFT_H
#define LOGID_ACTION_TOGGLESMARTSHIFT_H
#include <libconfig.h++>
#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<features::SmartShift> _smartshift;
};
}}
#endif //LOGID_ACTION_TOGGLESMARTSHIFT_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cmath>
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <algorithm>
#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> 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<ReleaseGesture>(device, setting);
}
std::string type = gesture_mode;
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
if(type == "onrelease")
return std::make_shared<ReleaseGesture>(device, setting);
else if(type == "oninterval" || type == "onfewpixels")
return std::make_shared<IntervalGesture>(device, setting);
else if(type == "axis")
return std::make_shared<AxisGesture>(device, setting);
else if(type == "nopress")
return std::make_shared<NullGesture>(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<ReleaseGesture>(device, setting);
}
} catch(libconfig::SettingNotFoundException& e) {
return std::make_shared<ReleaseGesture>(device, setting);
}
}
int16_t Gesture::Config::threshold() const
{
return _threshold;
}
std::shared_ptr<Action> Gesture::Config::action()
{
return _action;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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> action();
protected:
Device* _device;
std::shared_ptr<Action> _action;
int16_t _threshold;
};
static std::shared_ptr<Gesture> makeGesture(Device* device,
libconfig::Setting& setting);
protected:
explicit Gesture(Device* device);
Device* _device;
};
}}
#endif //LOGID_ACTION_GESTURE_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "Error.h"
const char *logid::backend::TimeoutError::what() const noexcept
{
return "Device timed out";
}

34
src/logid/backend/Error.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_ERROR_H
#define LOGID_BACKEND_ERROR_H
#include <stdexcept>
namespace logid {
namespace backend {
class TimeoutError: public std::exception
{
public:
TimeoutError() = default;
const char* what() const noexcept override;
};
}}
#endif //LOGID_BACKEND_ERROR_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_ERROR_H
#define LOGID_BACKEND_DJ_ERROR_H
#include <cstdint>
#include <stdexcept>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#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<raw::RawDevice>(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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> request(3);
request[0] = 3;
request[1] = index;
_hidpp10_device.setRegister(DevicePairing, request,
hidpp::ReportType::Short);
}
std::map<hidpp::DeviceIndex, uint8_t> Receiver::getDeviceActivity()
{
auto response = _hidpp10_device.getRegister(DeviceActivity, {},
hidpp::ReportType::Long);
std::map<hidpp::DeviceIndex, uint8_t> device_activity;
for(uint8_t i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; i++)
device_activity[static_cast<hidpp::DeviceIndex>(i)] = response[i];
return device_activity;
}
struct Receiver::PairingInfo
Receiver::getPairingInfo(hidpp::DeviceIndex index)
{
std::vector<uint8_t> 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<DeviceType::DeviceType>(response[7]);
return info;
}
struct Receiver::ExtendedPairingInfo
Receiver::getExtendedPairingInfo(hidpp::DeviceIndex index)
{
std::vector<uint8_t> 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<PowerSwitchLocation>(psl);
return info;
}
std::string Receiver::getDeviceName(hidpp::DeviceIndex index)
{
std::vector<uint8_t> 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<DeviceType::DeviceType>(
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<EventHandler>& 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<std::string, std::shared_ptr<EventHandler>>&
Receiver::djEventHandlers()
{
return _dj_event_handlers;
}
void Receiver::addHidppEventHandler(const std::string& nickname,
const std::shared_ptr<hidpp::EventHandler>& 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<std::string, std::shared_ptr<hidpp::EventHandler>>&
Receiver::hidppEventHandlers()
{
return _hidpp_event_handlers;
}
void Receiver::_sendDjRequest(hidpp::DeviceIndex index, uint8_t function,
const std::vector<uint8_t>&& 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<raw::RawEventHandler> hidpp_handler =
std::make_shared<raw::RawEventHandler>();
hidpp_handler->condition = [](std::vector<uint8_t>& 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<uint8_t>& 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<raw::RawEventHandler> dj_handler =
std::make_shared<raw::RawEventHandler>();
dj_handler->condition = [](std::vector<uint8_t>& report)->bool
{
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long);
};
dj_handler->callback = [this](std::vector<uint8_t>& 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<raw::RawDevice> Receiver::rawDevice() const
{
return _raw_device;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_RECEIVER_H
#define LOGID_BACKEND_DJ_RECEIVER_H
#include <cstdint>
#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<bool(Report&)> condition;
std::function<void(Report&)> 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<hidpp::DeviceIndex, uint8_t> 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<EventHandler>& handler);
void removeDjEventHandler(const std::string& nickname);
const std::map<std::string, std::shared_ptr<EventHandler>>&
djEventHandlers();
void addHidppEventHandler(const std::string& nickname,
const std::shared_ptr<hidpp::EventHandler>& handler);
void removeHidppEventHandler(const std::string& nickname);
const std::map<std::string, std::shared_ptr<hidpp::EventHandler>>&
hidppEventHandlers();
std::shared_ptr<raw::RawDevice> rawDevice() const;
private:
void _sendDjRequest(hidpp::DeviceIndex index, uint8_t function,
const std::vector<uint8_t>&& params);
void _handleDjEvent(dj::Report& report);
void _handleHidppEvent(hidpp::Report& report);
std::map<std::string, std::shared_ptr<EventHandler>>
_dj_event_handlers;
std::map<std::string, std::shared_ptr<hidpp::EventHandler>>
_hidpp_event_handlers;
std::shared_ptr<raw::RawDevice> _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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "ReceiverMonitor.h"
#include "../../util/task.h"
#include "../../util/log.h"
#include <utility>
#include <cassert>
using namespace logid::backend::dj;
ReceiverMonitor::ReceiverMonitor(std::string path) : _receiver (
std::make_shared<Receiver>(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<hidpp::EventHandler> event_handler =
std::make_shared<hidpp::EventHandler>();
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<raw::RawEventHandler>();
handler->condition = [index](std::vector<uint8_t>& report)->bool {
return report[Offset::DeviceIndex] == index;
};
handler->callback = [this, index, nickname](std::vector<uint8_t>& 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<Receiver> ReceiverMonitor::receiver() const
{
return _receiver;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_RECEIVERMONITOR_H
#define LOGID_BACKEND_DJ_RECEIVERMONITOR_H
#include <cstdint>
#include <string>
#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> receiver() const;
private:
std::shared_ptr<Receiver> _receiver;
};
}}}
#endif //LOGID_BACKEND_DJ_RECEIVERMONITOR_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <array>
#include <algorithm>
#include <cassert>
#include "Report.h"
using namespace logid::backend::dj;
using namespace logid::backend;
static const std::array<uint8_t, 34> 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<uint8_t, 39> 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<uint8_t>&& 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<uint8_t>& 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<Type>(_data[Offset::Type]);
}
hidpp::DeviceIndex Report::index() const
{
return static_cast<hidpp::DeviceIndex>(_data[Offset::DeviceIndex]);
}
uint8_t Report::feature() const
{
return _data[Offset::Feature];
}
std::vector<uint8_t>::iterator Report::paramBegin()
{
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t> Report::rawData() const
{
return _data;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_REPORT_H
#define LOGID_BACKEND_DJ_REPORT_H
#include <cstdint>
#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<uint8_t>&& rdesc);
class Report
{
public:
typedef ReportType::ReportType Type;
explicit Report(std::vector<uint8_t>& data);
Report(Type type, hidpp::DeviceIndex index, uint8_t feature);
Type type() const;
hidpp::DeviceIndex index() const;
uint8_t feature() const;
std::vector<uint8_t>::iterator paramBegin();
std::vector<uint8_t> rawData() const;
private:
std::vector<uint8_t> _data;
};
}}}
#endif //LOGID_BACKEND_DJ_REPORT_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_DJ_DEFS_H
#define LOGID_BACKEND_DJ_DEFS_H
#include <cstdint>
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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP_DEVICE_H
#define LOGID_BACKEND_HIDPP_DEVICE_H
#include <string>
#include <memory>
#include <functional>
#include <map>
#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<bool(Report&)> condition;
std::function<void(Report&)> 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::RawDevice> raw_device,
DeviceIndex index);
explicit Device(std::shared_ptr<dj::Receiver> receiver,
hidpp::DeviceConnectionEvent event);
~Device();
std::string devicePath() const;
DeviceIndex deviceIndex() const;
std::tuple<uint8_t, uint8_t> 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<EventHandler>& handler);
void removeEventHandler(const std::string& nickname);
const std::map<std::string, std::shared_ptr<EventHandler>>&
eventHandlers();
Report sendReport(Report& report);
void handleEvent(Report& report);
private:
void _init();
std::shared_ptr<raw::RawDevice> _raw_device;
std::shared_ptr<dj::Receiver> _receiver;
std::string _path;
DeviceIndex _index;
uint8_t _supported_reports;
std::tuple<uint8_t, uint8_t> _version;
uint16_t _pid;
std::string _name;
std::atomic<bool> _listening;
std::map<std::string, std::shared_ptr<EventHandler>> _event_handlers;
};
} } }
#endif //LOGID_BACKEND_HIDPP_DEVICE_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <array>
#include <algorithm>
#include <cassert>
#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<uint8_t, 22> 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<uint8_t, 22> 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<uint8_t, 22> 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<uint8_t, 22> 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<uint8_t>&& 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<uint8_t>& 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<Report::Type>(_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<hidpp::DeviceIndex>(_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<uint8_t>::iterator Report::paramBegin()
{
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t>::iterator Report::paramEnd()
{
return _data.end();
}
std::vector<uint8_t>::const_iterator Report::paramBegin() const
{
return _data.begin() + Offset::Parameters;
}
std::vector<uint8_t>::const_iterator Report::paramEnd() const
{
return _data.end();
}
void Report::setParams(const std::vector<uint8_t>& _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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP_REPORT_H
#define LOGID_BACKEND_HIDPP_REPORT_H
#include <cstdint>
#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<uint8_t>&& 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<uint8_t>& 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<uint8_t>::iterator paramBegin();
std::vector<uint8_t>::iterator paramEnd();
std::vector<uint8_t>::const_iterator paramBegin() const;
std::vector<uint8_t>::const_iterator paramEnd() const;
void setParams(const std::vector<uint8_t>& _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<uint8_t> rawReport () const { return _data; }
static constexpr std::size_t HeaderLength = 4;
private:
std::vector<uint8_t> _data;
};
}}}
#endif //LOGID_BACKEND_HIDPP_REPORT_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_HIDPP_SOFTWARE_ID 0
#include <cstdint>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#include <utility>
#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::RawDevice> raw_dev,
hidpp::DeviceIndex index) : hidpp::Device(std::move(raw_dev), index)
{
assert(version() == std::make_tuple(1, 0));
}
std::vector<uint8_t> Device::getRegister(uint8_t address,
const std::vector<uint8_t>& 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<uint8_t> Device::setRegister(uint8_t address,
const std::vector<uint8_t>& 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<uint8_t> Device::accessRegister(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t> &params)
{
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<uint8_t>(response.paramBegin(), response.paramEnd());
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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::RawDevice> raw_dev,
hidpp::DeviceIndex index);
std::vector<uint8_t> getRegister(uint8_t address,
const std::vector<uint8_t>& params, hidpp::Report::Type type);
std::vector<uint8_t> setRegister(uint8_t address,
const std::vector<uint8_t>& params, hidpp::Report::Type type);
private:
std::vector<uint8_t> accessRegister(uint8_t sub_id,
uint8_t address, const std::vector<uint8_t>& params);
};
}}}
#endif //LOGID_BACKEND_HIDPP10_DEVICE_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#include <string>
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP10_ERROR_H
#define LOGID_BACKEND_HIDPP10_ERROR_H
#include <cstdint>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#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::RawDevice> raw_device, hidpp::DeviceIndex index)
: hidpp::Device(raw_device, index)
{
assert(std::get<0>(version()) >= 2);
}
std::vector<uint8_t> Device::callFunction(uint8_t feature_index,
uint8_t function, std::vector<uint8_t>& 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<uint8_t>(response.paramBegin(), response.paramEnd());
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP20_DEVICE_H
#define LOGID_BACKEND_HIDPP20_DEVICE_H
#include "../hidpp/Device.h"
#include <cstdint>
namespace logid {
namespace backend {
namespace hidpp20 {
class Device : public hidpp::Device
{
public:
Device(std::string path, hidpp::DeviceIndex index);
Device(std::shared_ptr<raw::RawDevice> raw_device, hidpp::DeviceIndex index);
std::vector<uint8_t> callFunction(uint8_t feature_index,
uint8_t function,
std::vector<uint8_t>& params);
};
}}}
#endif //LOGID_BACKEND_HIDPP20_DEVICE_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP20_ERROR_H
#define LOGID_BACKEND_HIDPP20_ERROR_H
#include <stdexcept>
#include <cstdint>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#include "EssentialFeature.h"
#include "feature_defs.h"
#include "features/Root.h"
#include "Error.h"
using namespace logid::backend::hidpp20;
std::vector<uint8_t> EssentialFeature::callFunction(uint8_t function_id,
std::vector<uint8_t>& 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<uint8_t>(response.paramBegin(), response.paramEnd());
}
EssentialFeature::EssentialFeature(hidpp::Device* dev, uint16_t _id) :
_device (dev)
{
_index = hidpp20::FeatureID::ROOT;
if(_id)
{
std::vector<uint8_t> 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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
private:
hidpp::Device* _device;
uint8_t _index;
};
}}}
#endif //LOGID_BACKEND_HIDPP20_ESSENTIAL_FEATURE_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<uint8_t> Feature::callFunction(uint8_t function_id, std::vector<uint8_t>& 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<uint8_t> 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_H
#define LOGID_BACKEND_HIDPP20_FEATURE_H
#include <cstdint>
#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<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
private:
Device* _device;
uint8_t _index;
};
}}}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP20_FEATUREDEFS
#define LOGID_BACKEND_HIDPP20_FEATUREDEFS
#include <cstdint>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "AdjustableDPI.h"
using namespace logid::backend::hidpp20;
AdjustableDPI::AdjustableDPI(Device* dev) : Feature(dev, ID)
{
}
uint8_t AdjustableDPI::getSensorCount()
{
std::vector<uint8_t> params(0);
auto response = callFunction(GetSensorCount, params);
return response[0];
}
AdjustableDPI::SensorDPIList AdjustableDPI::getSensorDPIList(uint8_t sensor)
{
SensorDPIList dpi_list{};
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> params(3);
params[0] = sensor;
params[1] = (dpi >> 8);
params[2] = (dpi & 0xFF);
callFunction(SetSensorDPI, params);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<uint16_t> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cmath>
#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<uint8_t> params(0);
auto response = this->callFunction(Function::GetLength, params);
return response[0];
}
std::string _getName(uint8_t length,
const std::function<std::vector<uint8_t>(std::vector<uint8_t>)>& fcall)
{
uint8_t function_calls = length/hidpp::LongParamLength;
if(length % hidpp::LongParamLength)
function_calls++;
std::vector<uint8_t> 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<uint8_t> params)->std::vector<uint8_t> {
return this->callFunction(Function::GetDeviceName, params);
});
}
EssentialDeviceName::EssentialDeviceName(hidpp::Device* dev) :
EssentialFeature(dev, ID)
{
}
uint8_t EssentialDeviceName::getNameLength()
{
std::vector<uint8_t> params(0);
auto response = this->callFunction(DeviceName::Function::GetLength, params);
return response[0];
}
std::string EssentialDeviceName::getName()
{
return _getName(getNameLength(), [this]
(std::vector<uint8_t> params)->std::vector<uint8_t> {
return this->callFunction(DeviceName::Function::GetDeviceName, params);
});
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "FeatureSet.h"
using namespace logid::backend::hidpp20;
FeatureSet::FeatureSet(Device *device) : Feature(device, ID)
{
}
uint8_t FeatureSet::getFeatureCount()
{
std::vector<uint8_t> params(0);
auto response = callFunction(GetFeatureCount, params);
return response[0];
}
uint16_t FeatureSet::getFeature(uint8_t feature_index)
{
std::vector<uint8_t> 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<uint8_t, uint16_t> FeatureSet::getFeatures()
{
uint8_t feature_count = getFeatureCount();
std::map<uint8_t, uint16_t> features;
for(uint8_t i = 0; i < feature_count; i++)
features[i] = getFeature(i);
return features;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<uint8_t, uint16_t> getFeatures();
};
}}}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_FEATURESET_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#include "HiresScroll.h"
using namespace logid::backend::hidpp20;
HiresScroll::HiresScroll(Device *device) : Feature(device, ID)
{
}
HiresScroll::Capabilities HiresScroll::getCapabilities()
{
std::vector<uint8_t> 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<uint8_t> params(0);
auto response = callFunction(GetMode, params);
return response[0];
}
void HiresScroll::setMode(uint8_t mode)
{
std::vector<uint8_t> params(1);
params[0] = mode;
callFunction(SetMode, params);
}
bool HiresScroll::getRatchetState()
{
std::vector<uint8_t> 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<RatchetState>(report.paramBegin()[0]);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <cassert>
#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<x>(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> 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<ReprogControls>(dev);
}
uint8_t ReprogControls::getControlCount()
{
std::vector<uint8_t> params(0);
auto response = callFunction(GetControlCount, params);
return response[0];
}
ReprogControls::ControlInfo ReprogControls::getControlInfo(uint8_t index)
{
std::vector<uint8_t> 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<std::mutex> 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<uint16_t, ReprogControls::ControlInfo>&
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<uint16_t> ReprogControls::divertedButtonEvent(
const hidpp::Report& report)
{
assert(report.function() == DivertedButtonEvent);
std::set<uint16_t> 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<uint8_t> 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<uint8_t> 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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#define LOGID_BACKEND_HIDPP20_FEATURE_REPROGCONTROLS_H
#include <map>
#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<uint16_t, ControlInfo>& 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<uint16_t> divertedButtonEvent(const hidpp::Report&
report);
static Move divertedRawXYEvent(const hidpp::Report& report);
static std::shared_ptr<ReprogControls> autoVersion(Device *dev);
protected:
ReprogControls(Device* dev, uint16_t _id);
std::map<uint16_t, ControlInfo> _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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "Reset.h"
using namespace logid::backend::hidpp20;
Reset::Reset(Device *device) : Feature(device, ID)
{
}
uint16_t Reset::getProfile()
{
std::vector<uint8_t> 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<uint8_t> params(2);
params[0] = (profile >> 8) & 0xff;
params[1] = profile & 0xff;
callFunction(ResetToProfile, params);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "Root.h"
#include "../Error.h"
using namespace logid::backend::hidpp20;
Root::Root(Device* dev) : Feature(dev, ID)
{
}
std::vector<uint8_t> _genGetFeatureParams(uint16_t feature_id)
{
std::vector<uint8_t> params(2);
params[0] = feature_id & 0xff;
params[1] = (feature_id >> 8) & 0xff;
return params;
}
feature_info _genGetFeatureInfo(uint16_t feature_id,
std::vector<uint8_t> 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<uint8_t, uint8_t> Root::getVersion()
{
std::vector<uint8_t> 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<uint8_t, uint8_t> EssentialRoot::getVersion()
{
std::vector<uint8_t> params(0);
auto response = this->callFunction(Root::Function::Ping, params);
return std::make_tuple(response[0], response[1]);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<uint8_t, uint8_t> 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<uint8_t, uint8_t> getVersion();
};
}}}
#endif //LOGID_BACKEND_HIDPP20_FEATURE_ROOT_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "SmartShift.h"
using namespace logid::backend::hidpp20;
SmartShift::SmartShift(Device* dev) : Feature(dev, ID)
{
}
SmartShift::SmartshiftStatus SmartShift::getStatus()
{
std::vector<uint8_t> 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<uint8_t> 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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "DeviceMonitor.h"
#include "../../util/task.h"
#include "../../util/log.h"
#include "RawDevice.h"
#include "../hidpp/Device.h"
#include <thread>
#include <system_error>
extern "C"
{
#include <unistd.h>
#include <libudev.h>
}
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<std::mutex> 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<std::mutex> 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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_RAW_DEVICEMONITOR_H
#define LOGID_BACKEND_RAW_DEVICEMONITOR_H
#include <string>
#include <mutex>
#include <atomic>
extern "C"
{
#include <libudev.h>
}
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<bool> _run_monitor;
std::mutex _running;
};
}}}
#endif //LOGID_BACKEND_RAW_DEVICEMONITOR_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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 <string>
#include <system_error>
#include <utility>
#define MAX_DATA_LENGTH 32
extern "C"
{
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hidraw.h>
}
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<uint8_t> 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<uint8_t> 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<uint8_t>(rdesc.value, rdesc.value + rdesc.size);
}
std::vector<uint8_t> RawDevice::reportDescriptor() const
{
return _rdesc;
}
std::vector<uint8_t> RawDevice::sendReport(const std::vector<uint8_t>& 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<std::mutex> lock(send_report);
std::condition_variable cv;
bool top_of_queue = false;
auto task = std::make_shared<std::packaged_task<std::vector<uint8_t>()>>
( [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<uint8_t> response;
std::exception_ptr _exception;
std::shared_ptr<task> t = std::make_shared<task>(
[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<uint8_t>& 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<std::packaged_task<std::vector<uint8_t>()>>
([this, report]() {
this->_sendReport(report);
return std::vector<uint8_t>();
});
auto f = task->get_future();
_io_queue.push(task);
f.get();
}
else
_sendReport(report);
}
std::vector<uint8_t> RawDevice::_respondToReport
(const std::vector<uint8_t>& request)
{
_sendReport(request);
_continue_respond = true;
auto start_point = std::chrono::steady_clock::now();
while(_continue_respond) {
std::vector<uint8_t> response;
auto current_point = std::chrono::steady_clock::now();
auto timeout = global_config->ioTimeout() - std::chrono::duration_cast
<std::chrono::milliseconds>(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<uint8_t>& report)
{
std::lock_guard<std::mutex> 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<uint8_t> &report,
std::size_t maxDataLength)
{
return _readReport(report, maxDataLength, global_config->ioTimeout());
}
int RawDevice::_readReport(std::vector<uint8_t> &report,
std::size_t maxDataLength, std::chrono::milliseconds timeout)
{
std::lock_guard<std::mutex> lock(_dev_io);
int ret;
report.resize(maxDataLength);
timeval timeout_tv{};
timeout_tv.tv_sec = duration_cast<seconds>(global_config->ioTimeout())
.count();
timeout_tv.tv_usec = duration_cast<microseconds>(
global_config->ioTimeout()).count() %
duration_cast<microseconds>(seconds(1)).count();
auto timeout_ms = duration_cast<milliseconds>(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<std::mutex> lock(_dev_io);
}
void RawDevice::listen()
{
std::lock_guard<std::mutex> 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<uint8_t> 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<std::mutex> 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<raw::RawEventHandler>& handler)
{
std::unique_lock<std::mutex> 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<std::mutex> lock(_event_handler_lock);
_event_handlers.erase(nickname);
}
const std::map<std::string, std::shared_ptr<raw::RawEventHandler>>&
RawDevice::eventHandlers()
{
std::unique_lock<std::mutex> lock(_event_handler_lock);
return _event_handlers;
}
void RawDevice::_handleEvent(std::vector<uint8_t> &report)
{
std::unique_lock<std::mutex> 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_RAWDEVICE_H
#define LOGID_BACKEND_RAWDEVICE_H
#include <string>
#include <vector>
#include <mutex>
#include <map>
#include <atomic>
#include <future>
#include <set>
#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<uint8_t> getReportDescriptor(std::string path);
static std::vector<uint8_t> getReportDescriptor(int fd);
std::vector<uint8_t> reportDescriptor() const;
std::vector<uint8_t> sendReport(const std::vector<uint8_t>& report);
void sendReportNoResponse(const std::vector<uint8_t>& report);
void interruptRead();
void listen();
void listenAsync();
void stopListener();
bool isListening();
void addEventHandler(const std::string& nickname,
const std::shared_ptr<RawEventHandler>& handler);
void removeEventHandler(const std::string& nickname);
const std::map<std::string, std::shared_ptr<RawEventHandler>>&
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<uint8_t> _rdesc;
std::atomic<bool> _continue_listen;
std::atomic<bool> _continue_respond;
std::condition_variable _listen_condition;
std::map<std::string, std::shared_ptr<RawEventHandler>>
_event_handlers;
std::mutex _event_handler_lock;
void _handleEvent(std::vector<uint8_t>& report);
/* These will only be used internally and processed with a queue */
int _sendReport(const std::vector<uint8_t>& report);
int _readReport(std::vector<uint8_t>& report, std::size_t maxDataLength);
int _readReport(std::vector<uint8_t>& report, std::size_t maxDataLength,
std::chrono::milliseconds timeout);
std::vector<uint8_t> _respondToReport(const std::vector<uint8_t>&
request);
mutex_queue<std::shared_ptr<std::packaged_task<std::vector<uint8_t>()>>>
_io_queue;
};
}}}
#endif //LOGID_BACKEND_RAWDEVICE_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_RAW_DEFS_H
#define LOGID_BACKEND_RAW_DEFS_H
#include <functional>
#include <cstdint>
#include <vector>
namespace logid {
namespace backend {
namespace raw
{
struct RawEventHandler
{
std::function<bool(std::vector<uint8_t>& )> condition;
std::function<void(std::vector<uint8_t>& )> callback;
};
}}}
#endif //LOGID_BACKEND_RAW_DEFS_H

140
src/logid/features/DPI.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <algorithm>
#include <cmath>
#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];
}

53
src/logid/features/DPI.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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<uint16_t> _dpis;
};
private:
Config _config;
backend::hidpp20::AdjustableDPI _adjustable_dpi;
std::vector<backend::hidpp20::AdjustableDPI::SensorDPIList> _dpi_lists;
};
}}
#endif //LOGID_FEATURE_DPI_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_FEATURES_DEVICEFEATURE_H
#define LOGID_FEATURES_DEVICEFEATURE_H
#include <string>
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

Some files were not shown because too many files have changed in this diff Show More