parent
2190b527a7
commit
9c092edcf3
17 changed files with 1653 additions and 0 deletions
@ -0,0 +1,14 @@ |
||||
cmake_minimum_required(VERSION 3.14) |
||||
project(logiops) |
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") |
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -Wall") |
||||
|
||||
set(CMAKE_CXX_STANDARD 14) |
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) |
||||
|
||||
add_subdirectory(src/logid) |
||||
|
||||
if(NOT EXISTS "${PROJECT_BINARY_DIR}/logid.cfg") |
||||
configure_file("logid.example.cfg" "logid.cfg" COPYONLY) |
||||
endif() |
@ -0,0 +1,32 @@ |
||||
# logiops |
||||
|
||||
This is an unofficial driver for Logitech mice and keyboard. |
||||
|
||||
This is currently only compatible with HID++ \>2.0 devices. |
||||
|
||||
## Building |
||||
|
||||
This project requires a C++14 compiler, cmake, libevdev, libconfig, and [libhidpp](https://github.com/cvuchener/hidpp) |
||||
|
||||
To build this project, run: |
||||
|
||||
``` |
||||
mkdir build |
||||
cd build |
||||
cmake .. |
||||
make |
||||
``` |
||||
|
||||
Installation is currently not implemented. |
||||
|
||||
## Compatible Devices |
||||
|
||||
| Device | Compatible? | |
||||
|:---------:|:-----------:| |
||||
| MX Master | Yes | |
||||
| T400 | Untested | |
||||
| K400r | Untested | |
||||
| K350 | Untested | |
||||
| M325c | Untested | |
||||
|
||||
I own the MX Master, T400, K400r, K350, and M325c. Feel free to add to this list if you can test any additional devices. |
@ -0,0 +1,71 @@ |
||||
devices: ( |
||||
{ |
||||
name: "MX Master"; |
||||
smartshift: |
||||
{ |
||||
on: true; |
||||
threshold: 30; |
||||
}; |
||||
hiresscroll: true; |
||||
dpi: 1000; |
||||
|
||||
buttons: ( |
||||
{ |
||||
cid: 0xc3; |
||||
action = |
||||
{ |
||||
type: "Gestures"; |
||||
gestures: ( |
||||
{ |
||||
direction: "Up"; |
||||
mode: "OnRelease"; |
||||
action = |
||||
{ |
||||
type: "Keypress"; |
||||
keys: ["KEY_UP"]; |
||||
}; |
||||
}, |
||||
{ |
||||
direction: "Down"; |
||||
mode: "OnRelease"; |
||||
action = |
||||
{ |
||||
type: "Keypress"; |
||||
keys: ["KEY_DOWN"]; |
||||
}; |
||||
}, |
||||
{ |
||||
direction: "Left"; |
||||
mode: "OnRelease"; |
||||
action = |
||||
{ |
||||
type: "CycleDPI"; |
||||
dpis: [400, 600, 800, 1000, 1200, 1400, 1600]; |
||||
}; |
||||
}, |
||||
{ |
||||
direction: "Right"; |
||||
mode: "OnRelease"; |
||||
action = |
||||
{ |
||||
type = "ToggleSmartshift"; |
||||
} |
||||
}, |
||||
{ |
||||
direction: "None" |
||||
mode: "NoPress" |
||||
} |
||||
); |
||||
}; |
||||
}, |
||||
{ |
||||
cid: 0xc4; |
||||
action = |
||||
{ |
||||
type: "Keypress"; |
||||
keys: ["KEY_A"]; |
||||
}; |
||||
} |
||||
); |
||||
} |
||||
); |
@ -0,0 +1,154 @@ |
||||
#include <hidpp20/Error.h> |
||||
#include <hidpp/SimpleDispatcher.h> |
||||
#include <hidpp20/IAdjustableDPI.h> |
||||
|
||||
#include "Actions.h" |
||||
#include "util.h" |
||||
#include "EvdevDevice.h" |
||||
|
||||
KeyAction::KeyAction(const KeyAction &a, Device* d) : ButtonAction(Action::Keypress) |
||||
{ |
||||
device = d; |
||||
std::copy(a.keys.begin(), a.keys.end(), std::back_inserter(keys)); |
||||
} |
||||
|
||||
void KeyAction::press() |
||||
{ |
||||
//KeyPress event for each in keys
|
||||
for(unsigned int i : keys) |
||||
global_evdev->send_event(EV_KEY, i, 1); |
||||
} |
||||
|
||||
void KeyAction::release() |
||||
{ |
||||
//KeyRelease event for each in keys
|
||||
for(unsigned int i : keys) |
||||
global_evdev->send_event(EV_KEY, i, 0); |
||||
} |
||||
|
||||
void GestureAction::press() |
||||
{ |
||||
held = true; |
||||
x = 0; |
||||
y = 0; |
||||
} |
||||
|
||||
void GestureAction::move(HIDPP20::IReprogControlsV4::Move m) |
||||
{ |
||||
x += m.x; |
||||
y += m.y; |
||||
} |
||||
|
||||
void GestureAction::release() |
||||
{ |
||||
held = false; |
||||
auto direction = get_direction(x, y); |
||||
|
||||
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() |
||||
{ |
||||
if(device->features.find(0x2110) == device->features.end()) |
||||
{ |
||||
log_printf(DEBUG, "Error toggling smart shift, feature is non-existent."); |
||||
return; |
||||
} |
||||
const uint8_t f_index = device->features.find(0x2110)->second; |
||||
std::vector<uint8_t> results; |
||||
|
||||
try |
||||
{ |
||||
results = device->hidpp_dev->callFunction(f_index, 0x00); |
||||
if(results[0] == 0x02) |
||||
results = device->hidpp_dev->callFunction(f_index, 0x01, {0x01}); |
||||
else if(results[0] == 0x01) |
||||
results = device->hidpp_dev->callFunction(f_index, 0x01, {0x02}); |
||||
else |
||||
results = device->hidpp_dev->callFunction(f_index, 0x01, {0x01}); |
||||
} |
||||
catch(HIDPP20::Error &e) |
||||
{ |
||||
log_printf(ERROR, "Error toggling smart shift, code %d: %s\n", e.errorCode(), e.what()); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
void HiresScrollAction::press() |
||||
{ |
||||
if(device->features.find(0x2110) == device->features.end()) |
||||
{ |
||||
log_printf(DEBUG, "Error toggling hires scroll, feature is non-existent."); |
||||
return; |
||||
} |
||||
const uint8_t f_index = device->features.find(0x2121)->second; |
||||
std::vector<uint8_t> results; |
||||
|
||||
try |
||||
{ |
||||
results = device->hidpp_dev->callFunction(f_index, 0x01); |
||||
if(results[0] == 0x02) |
||||
results = device->hidpp_dev->callFunction(f_index, 0x02, {0x00}); |
||||
else |
||||
results = device->hidpp_dev->callFunction(f_index, 0x02, {0x02}); |
||||
} |
||||
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()); |
||||
} |
||||
} |
@ -0,0 +1,122 @@ |
||||
#ifndef LOGIOPS_ACTIONS_H |
||||
#define LOGIOPS_ACTIONS_H |
||||
|
||||
#include <map> |
||||
#include <hidpp20/IReprogControlsV4.h> |
||||
#include "Device.h" |
||||
|
||||
enum class Action |
||||
{ |
||||
None, |
||||
Keypress, |
||||
Gestures, |
||||
CycleDPI, |
||||
ChangeDPI, |
||||
ToggleSmartshift, |
||||
ToggleHiresScroll |
||||
}; |
||||
enum class Direction |
||||
{ |
||||
None, |
||||
Up, |
||||
Down, |
||||
Left, |
||||
Right |
||||
}; |
||||
enum class GestureMode |
||||
{ |
||||
NoPress, |
||||
OnRelease, |
||||
OnFewPixels |
||||
}; |
||||
|
||||
class Device; |
||||
|
||||
class ButtonAction |
||||
{ |
||||
public: |
||||
Action type; |
||||
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 void press() {} |
||||
virtual void release() {} |
||||
}; |
||||
class KeyAction : public ButtonAction |
||||
{ |
||||
public: |
||||
explicit KeyAction(std::vector<unsigned int> k) : ButtonAction(Action::Keypress), keys (std::move(k)) {}; |
||||
KeyAction(const KeyAction &a, Device* d); |
||||
//virtual KeyAction* create_instance(Device* d) { return new KeyAction(*this, d); };
|
||||
virtual void press(); |
||||
virtual void release(); |
||||
private: |
||||
std::vector<unsigned int> keys; |
||||
}; |
||||
class Gesture |
||||
{ |
||||
public: |
||||
Gesture(ButtonAction* ba, GestureMode m, int pp=0) : action (ba), mode (m), per_pixel (pp) {}; |
||||
Gesture(const Gesture &g) : action (g.action), mode (g.mode), per_pixel (g.per_pixel) {}; |
||||
|
||||
ButtonAction* action; |
||||
|
||||
GestureMode mode; |
||||
int per_pixel; |
||||
}; |
||||
class GestureAction : public ButtonAction |
||||
{ |
||||
public: |
||||
GestureAction(std::map<Direction, Gesture*> g) : ButtonAction(Action::Gestures), gestures (std::move(g)) {}; |
||||
std::map<Direction, Gesture*> gestures; |
||||
virtual void press(); |
||||
void move(HIDPP20::IReprogControlsV4::Move m); |
||||
virtual void release(); |
||||
private: |
||||
bool held; |
||||
int x; |
||||
int y; |
||||
}; |
||||
class SmartshiftAction : public ButtonAction |
||||
{ |
||||
public: |
||||
SmartshiftAction() : ButtonAction(Action::ToggleSmartshift) {}; |
||||
virtual void press(); |
||||
virtual void release() {} |
||||
}; |
||||
class HiresScrollAction : public ButtonAction |
||||
{ |
||||
public: |
||||
HiresScrollAction() : ButtonAction(Action::ToggleHiresScroll) {}; |
||||
virtual void press(); |
||||
virtual void release() {} |
||||
}; |
||||
class CycleDPIAction : public ButtonAction |
||||
{ |
||||
public: |
||||
CycleDPIAction(std::vector<int> d) : ButtonAction(Action::CycleDPI), dpis (d) {}; |
||||
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 void press(); |
||||
virtual void release() {} |
||||
private: |
||||
int dpi_inc; |
||||
}; |
||||
|
||||
#endif //LOGIOPS_ACTIONS_H
|
@ -0,0 +1,41 @@ |
||||
cmake_minimum_required(VERSION 3.14) |
||||
project(logid) |
||||
|
||||
set(CMAKE_CXX_STANDARD 14) |
||||
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../CMake") |
||||
|
||||
find_package(Threads REQUIRED) |
||||
find_package(PkgConfig REQUIRED) |
||||
|
||||
add_executable(logid |
||||
logid.cpp |
||||
util.cpp |
||||
util.h |
||||
Configuration.cpp |
||||
Configuration.h |
||||
Actions.cpp |
||||
Actions.h |
||||
Device.cpp |
||||
Device.h |
||||
DeviceFinder.cpp |
||||
DeviceFinder.h |
||||
EvdevDevice.cpp |
||||
EvdevDevice.h) |
||||
set_target_properties(logid PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) |
||||
|
||||
pkg_check_modules(PC_EVDEV libevdev) |
||||
pkg_check_modules(libhidpp REQUIRED) |
||||
pkg_check_modules(libconfig++ REQUIRED) |
||||
|
||||
find_path(HIDPP_INCLUDE_DIR hidpp) |
||||
find_library(HIDPP_LIBRARY libhidpp.so) |
||||
|
||||
find_path(EVDEV_INCLUDE_DIR libevdev/libevdev.h |
||||
HINTS ${PC_EVDEV_INCLUDE_DIRS} ${PC_EVDEV_INCLUDEDIR}) |
||||
find_library(EVDEV_LIBRARY |
||||
NAMES evdev libevdev) |
||||
|
||||
include_directories(${HIDPP_INCLUDE_DIR}/hidpp ${EVDEV_INCLUDE_DIR}) |
||||
|
||||
target_link_libraries(logid ${CMAKE_THREAD_LIBS_INIT} ${EVDEV_LIBRARY} config++ ${HIDPP_LIBRARY}) |
@ -0,0 +1,386 @@ |
||||
#include <cstdint> |
||||
#include <vector> |
||||
#include <map> |
||||
#include <linux/input-event-codes.h> |
||||
#include <libevdev/libevdev.h> |
||||
#include <algorithm> |
||||
#include <cstring> |
||||
|
||||
#include "Configuration.h" |
||||
#include "util.h" |
||||
|
||||
using namespace libconfig; |
||||
|
||||
Configuration::Configuration(const char *config_file) |
||||
{ |
||||
//Read config file
|
||||
try |
||||
{ |
||||
cfg.readFile(config_file); |
||||
} |
||||
catch(const FileIOException &e) |
||||
{ |
||||
log_printf(ERROR, "%s", "I/O Error while reading configuration file!"); |
||||
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(); |
||||
Setting* _devices; |
||||
|
||||
try { _devices = &root["devices"]; } |
||||
catch(const SettingNotFoundException &e) |
||||
{ |
||||
log_printf(WARN, "No devices listed in config file."); |
||||
return; |
||||
} |
||||
|
||||
for(int i = 0; i < _devices->getLength(); i++) |
||||
{ |
||||
const Setting &device = (*_devices)[i]; |
||||
std::string name; |
||||
try |
||||
{ |
||||
if(!device.lookupValue("name", name)) |
||||
{ |
||||
log_printf(WARN, "Line %d: 'name' must be a string, skipping device.", device["name"].getSourceLine()); |
||||
continue; |
||||
} |
||||
} |
||||
catch(SettingNotFoundException &e) |
||||
{ |
||||
log_printf(WARN, "Line %d: Missing 'name' field, skipping device.", device.getSourceLine()); |
||||
continue; |
||||
} |
||||
devices.insert({name, new DeviceConfig(device)}); |
||||
} |
||||
} |
||||
|
||||
DeviceConfig::DeviceConfig(const libconfig::Setting &root) |
||||
{ |
||||
try |
||||
{ |
||||
int d; |
||||
if(!root.lookupValue("dpi", d)) |
||||
throw SettingTypeException(root["dpi"]); |
||||
dpi = new int(d); |
||||
} |
||||
catch(const SettingNotFoundException &e) |
||||
{ |
||||
log_printf(INFO, "Missing dpi option, not setting."); |
||||
} |
||||
catch(const SettingTypeException &e) |
||||
{ |
||||
log_printf(WARN, "Line %d: DPI must me an integer; not setting.", root["dpi"].getSourceLine()); |
||||
} |
||||
|
||||
try |
||||
{ |
||||
bool b; |
||||
if(!root.lookupValue("hiresscroll", b)) |
||||
throw SettingTypeException(root["hiresscroll"]); |
||||
hiresscroll = new bool(b); |
||||
} |
||||
catch(const SettingNotFoundException &e) |
||||
{ |
||||
log_printf(INFO, "Missing hiresscroll option, not setting."); |
||||
} |
||||
catch(const SettingTypeException &e) |
||||
{ |
||||
log_printf(WARN, "Line %d: DPI must me an integer; not setting.", root["hiresscroll"].getSourceLine()); |
||||
} |
||||
|
||||
try |
||||
{ |
||||
const Setting& ss = root["smartshift"]; |
||||
smartshift = new smartshift_options; |
||||
bool on; |
||||
int threshold; |
||||
try |
||||
{ |
||||
if (ss.lookupValue("on", on)) smartshift->on = 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 0."); |
||||
} |
||||
if(threshold >= 100) |
||||
{ |
||||
threshold = 99; |
||||
log_printf(INFO, "Smartshift threshold must be > 0 or < 100, setting to 99."); |
||||
} |
||||
smartshift->threshold = new uint8_t(threshold); |
||||
} |
||||
else log_printf(WARN, "Line %d: threshold must be an integer", ss["threshold"].getSourceLine()); |
||||
} |
||||
catch(const SettingNotFoundException &e) |
||||
{ |
||||
log_printf(INFO, "Missing threshold for smartshift, not setting."); |
||||
} |
||||
} |
||||
catch(const SettingNotFoundException &e) { } |
||||
catch(const SettingTypeException &e) |
||||
{ |
||||
log_printf(WARN, "Line %d: smartshift field must be an object", root["hiressscroll"].getSourceLine()); |
||||
} |
||||
|
||||
Setting* buttons; |
||||
try |
||||
{ |
||||
buttons = &root["buttons"]; |
||||
} |
||||
catch(const SettingNotFoundException &e) |
||||
{ |
||||
log_printf(WARN, "No button configuration found, reverting to null config."); |
||||
new std::map<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 = string_to_action(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* 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 = string_to_direction(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 = string_to_gesturemode(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; |
||||
} |
||||
|
||||
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 = string_to_action(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; |
||||
gesture_config.lookupValue("pixels", pp); |
||||
gestures.insert({direction, new Gesture(ba, mode, pp)}); |
||||
} |
||||
catch(SettingNotFoundException &e) |
||||
{ |
||||
log_printf(WARN, "Line %d: OnFewPixels requires a 'pixels' field.", gesture_config.getSourceLine()); |
||||
} |
||||
} |
||||
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() |
||||
{ |
||||
dpi = nullptr; |
||||
hiresscroll = nullptr; |
||||
smartshift = nullptr; |
||||
actions = {}; |
||||
} |
@ -0,0 +1,41 @@ |
||||
#ifndef MASTEROPTIONS_CONFIGURATION_H |
||||
#define MASTEROPTIONS_CONFIGURATION_H |
||||
|
||||
#include <map> |
||||
#include <libconfig.h++> |
||||
|
||||
struct smartshift_options |
||||
{ |
||||
bool* on = nullptr; |
||||
uint8_t* threshold = nullptr; |
||||
}; |
||||
|
||||
class DeviceConfig; |
||||
class ButtonAction; |
||||
enum class Action; |
||||
|
||||
class DeviceConfig |
||||
{ |
||||
public: |
||||
DeviceConfig(); |
||||
DeviceConfig(const libconfig::Setting& root); |
||||
const int* dpi = nullptr; |
||||
struct smartshift_options* smartshift = nullptr; |
||||
const bool* hiresscroll = nullptr; |
||||
std::map<uint16_t, ButtonAction*> actions; |
||||
}; |
||||
|
||||
class Configuration |
||||
{ |
||||
public: |
||||
Configuration(const char* config_file); |
||||
std::map<std::string, DeviceConfig*> devices; |
||||
private: |
||||
libconfig::Config cfg; |
||||
}; |
||||
|
||||
ButtonAction* parse_action(Action action, const libconfig::Setting* action_config, bool is_gesture=false); |
||||
|
||||
extern Configuration* global_config; |
||||
|
||||
#endif //MASTEROPTIONS_CONFIGURATION_H
|
@ -0,0 +1,292 @@ |
||||
#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/IReprogControlsV4.h> |
||||
#include <hidpp20/Device.h> |
||||
#include <hidpp10/Error.h> |
||||
#include <algorithm> |
||||
#include <cstring> |
||||
|
||||
#include "Device.h" |
||||
#include "Actions.h" |
||||
#include "Configuration.h" |
||||
#include "util.h" |
||||
#include "EvdevDevice.h" |
||||
|
||||
using namespace std::chrono_literals; |
||||
|
||||
Device::Device(std::string p, const HIDPP::DeviceIndex i) : path(p), index (i) |
||||
{ |
||||
DeviceRemoved = false; |
||||
dispatcher = new HIDPP::SimpleDispatcher(path.c_str()); |
||||
hidpp_dev = new HIDPP20::Device(dispatcher, index); |
||||
features = get_features(); |
||||
|
||||
if(global_config->devices.find(hidpp_dev->name()) == global_config->devices.end()) |
||||
{ |
||||
log_printf(INFO, "Device %s not configured, using default.", hidpp_dev->name().c_str()); |
||||
config = new DeviceConfig(); |
||||
} |
||||
else config = global_config->devices.find(hidpp_dev->name())->second; |
||||
} |
||||
|
||||
void Device::configure(bool scanning) |
||||
{ |
||||
if(config->dpi != nullptr) |
||||
set_dpi(*config->dpi, scanning); |
||||
if(config->smartshift != nullptr) |
||||
set_smartshift(*config->smartshift, scanning); |
||||
if(config->hiresscroll != nullptr) |
||||
set_hiresscroll(*config->hiresscroll, scanning); |
||||
divert_buttons(); |
||||
} |
||||
|
||||
void Device::divert_buttons(bool scanning) |
||||
{ |
||||
HIDPP20::IReprogControlsV4 irc4(hidpp_dev); |
||||
for(auto it = config->actions.begin(); it != config->actions.end(); ++it) |
||||
{ |
||||
uint8_t flags = 0; |
||||
flags |= HIDPP20::IReprogControlsV4::ChangeTemporaryDivert; |
||||
flags |= HIDPP20::IReprogControlsV4::TemporaryDiverted; |
||||
if(it->second->type == Action::Gestures) |
||||
{ |
||||
flags |= HIDPP20::IReprogControlsV4::ChangeRawXYDivert; |
||||
flags |= HIDPP20::IReprogControlsV4::RawXYDiverted; |
||||
} |
||||
it->first; |
||||
irc4.setControlReporting(it->first, flags, it->first); |
||||
} |
||||
} |
||||
|
||||
void Device::set_smartshift(smartshift_options ops, bool scanning) |
||||
{ |
||||
std::vector<uint8_t> parameters; |
||||
parameters.push_back(ops.on == nullptr ? 0 : (*ops.on)? 2 : 1); |
||||
if(ops.threshold != nullptr) |
||||
{ |
||||
parameters.push_back(*ops.threshold); |
||||
parameters.push_back(*ops.threshold); |
||||
} |
||||
|
||||
if(features.find(0x2110) == features.end()) |
||||
{ |
||||
log_printf(DEBUG, "Error toggling smart shift, feature is non-existent."); |
||||
return; |
||||
} |
||||
const uint8_t f_index = features.find(0x2110)->second; |
||||
|
||||
try { hidpp_dev->callFunction(f_index, 0x01, parameters); } |
||||
catch (HIDPP20::Error &e) |
||||
{ |
||||
if(scanning) |
||||
throw e; |
||||
log_printf(ERROR, "Error setting smartshift options, code %d: %s\n", e.errorCode(), e.what()); |
||||
} |
||||
} |
||||
|
||||
void Device::set_hiresscroll(bool b, bool scanning) |
||||
{ |
||||
|
||||
} |
||||
|
||||
void Device::set_dpi(int dpi, bool scanning) |
||||
{ |
||||
HIDPP20::IAdjustableDPI iad(hidpp_dev); |
||||
|
||||
try { for(int i = 0; i < iad.getSensorCount(); i++) iad.setSensorDPI(i, dpi); } |
||||
catch (HIDPP20::Error &e) |
||||
{ |
||||
if(scanning) |
||||
throw e; |
||||
log_printf(ERROR, "Error while setting DPI: %s", e.what()); |
||||
} |
||||
} |
||||
|
||||
void Device::start() |
||||
{ |
||||
configure(); |
||||
|
||||
auto *d = new HIDPP::SimpleDispatcher(path.c_str()); |
||||
listener = new SimpleListener(d, index); |
||||
listener->addEventHandler( std::make_unique<ButtonHandler>(hidpp_dev, this) ); |
||||
auto listener_thread = std::thread { [=]() { listener->start(); } }; |
||||
listener_thread.detach(); |
||||
while(!DeviceRemoved) |
||||
{ |
||||
std::mutex m; |
||||
std::condition_variable cv; |
||||
std::vector<uint8_t> results; |
||||
|
||||
std::thread t([&cv, &results, dev=hidpp_dev, removed=&DeviceRemoved]() |
||||
{ |
||||
try { results = dev->callFunction(0x00, 0x00); } |
||||
catch(HIDPP10::Error &e) { usleep(500000); } |
||||
catch(std::system_error &e) |
||||
{ |
||||
cv.notify_one(); |
||||
if(*removed) printf("REMOVED!\n"); |
||||
*removed = true; |
||||
} |
||||
cv.notify_one(); |
||||
}); |
||||
t.detach(); |
||||
|
||||
std::unique_lock<std::mutex> l(m); |
||||
if(cv.wait_for(l, 500ms) == std::cv_status::timeout) |
||||
{ |
||||
while(!DeviceRemoved) |
||||
{ |
||||
try |
||||
{ |
||||
configure(true); |
||||
break; |
||||
} |
||||
catch(std::exception &e) {} // Retry infinitely on failure
|
||||
} |
||||
} |
||||
usleep(200000); |
||||
} |
||||
|
||||
listener->stop(); |
||||
listener_thread.join(); |
||||
} |
||||
|
||||
void Device::ButtonHandler::handleEvent (const HIDPP::Report &event) |
||||
{ |
||||
switch (event.function()) |
||||
{ |
||||
case HIDPP20::IReprogControlsV4::Event::DivertedButtonEvent: |
||||
{ |
||||
new_states = HIDPP20::IReprogControlsV4::divertedButtonEvent(event); |
||||
if (states.empty()) |
||||
{ |
||||
for (uint16_t i : new_states) |
||||
std::thread{[=]() { dev->press_button(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(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->press_button(i); }}.detach(); |
||||
} else |
||||
std::thread{[=]() { dev->release_button(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->move_diverted(i, raw_xy); }}.detach(); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void Device::EventListener::removeEventHandlers () |
||||
{ |
||||
for (const auto &p: iterators) |
||||
dispatcher->unregisterEventHandler(p.second); |
||||
handlers.clear(); |
||||
iterators.clear(); |
||||
} |
||||
|
||||
Device::EventListener::~EventListener() |
||||
{ |
||||
removeEventHandlers(); |
||||
} |
||||
|
||||
void Device::EventListener::addEventHandler(std::unique_ptr<EventHandler> &&handler) |
||||
{ |
||||
uint8_t feature = handler->feature()->index(); |
||||
EventHandler *ptr = handler.get(); |
||||
handlers.emplace(feature, std::move(handler)); |
||||
dispatcher->registerEventHandler(index, feature, [ptr](const HIDPP::Report &report) |
||||
{ |
||||
ptr->handleEvent(report); |
||||
return true; |
||||
}); |
||||
} |
||||
|
||||
void Device::SimpleListener::start() |
||||
{ |
||||
try { dispatcher->listen(); } |
||||
catch(std::system_error &e) { } |
||||
} |
||||
|
||||
void Device::SimpleListener::stop() |
||||
{ |
||||
dispatcher->stop(); |
||||
} |
||||
|
||||
bool Device::SimpleListener::event (EventHandler *handler, const HIDPP::Report &report) |
||||
{ |
||||
handler->handleEvent (report); |
||||
return true; |
||||
} |
||||
|
||||
void Device::stop() |
||||
{ |
||||
DeviceRemoved = true; |
||||
} |
||||
|
||||
void Device::press_button(uint16_t cid) |
||||
{ |
||||
if(config->actions.find(cid) == config->actions.end()) |
||||
{ |
||||
log_printf(WARN, "0x%x was pressed but no action was found.", cid); |
||||
return; |
||||
} |
||||
config->actions.find(cid)->second->press(); |
||||
} |
||||
|
||||
void Device::release_button(uint16_t cid) |
||||
{ |
||||
if(config->actions.find(cid) == config->actions.end()) |
||||
{ |
||||
log_printf(WARN, "0x%x was released but no action was found.", cid); |
||||
return; |
||||
} |
||||
config->actions.find(cid)->second->release(); |
||||
} |
||||
|
||||
void Device::move_diverted(uint16_t cid, HIDPP20::IReprogControlsV4::Move m) |
||||
{ |
||||
auto action = config->actions.find(cid)->second; |
||||
switch(action->type) |
||||
{ |
||||
case Action::Gestures: |
||||
((GestureAction*)action)->move(m); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
std::map<uint16_t, uint8_t> Device::get_features() |
||||
{ |
||||
std::map<uint16_t, uint8_t> features; |
||||
|
||||
HIDPP20::IFeatureSet ifs (hidpp_dev); |
||||
|
||||
unsigned int feature_count = ifs.getCount(); |
||||
|
||||
for(unsigned int i = 1; i <= feature_count; i++) |
||||
features.insert({ifs.getFeatureID(i), i}); |
||||
|
||||
return features; |
||||
} |
@ -0,0 +1,109 @@ |
||||
#ifndef LOGIOPS_DEVICE_H |
||||
#define LOGIOPS_DEVICE_H |
||||
|
||||
#include <map> |
||||
#include <memory> |
||||
#include <hidpp/Dispatcher.h> |
||||
#include <hidpp/SimpleDispatcher.h> |
||||
|
||||
#include "Actions.h" |
||||
#include "Configuration.h" |
||||
|
||||
class Device |
||||
{ |
||||
public: |
||||
Device(std::string p, const HIDPP::DeviceIndex i); |
||||
|
||||
void configure(bool scanning=false); |
||||
|
||||
void press_button(uint16_t cid); |
||||
void release_button(uint16_t cid); |
||||
void move_diverted(uint16_t cid, HIDPP20::IReprogControlsV4::Move move); |
||||
|
||||
void start(); |
||||
void stop(); |
||||
|
||||
std::map<uint16_t, uint8_t> get_features(); |
||||
|
||||
std::map<uint16_t, uint8_t> features; |
||||
|
||||
std::string path; |
||||
const HIDPP::DeviceIndex index; |
||||
HIDPP::Dispatcher* dispatcher; |
||||
HIDPP20::Device* hidpp_dev; |
||||
|
||||
class EventHandler |
||||
{ |
||||
public: |
||||
virtual const HIDPP20::FeatureInterface *feature() const = 0; |
||||
virtual void handleEvent (const HIDPP::Report &event) = 0; |
||||
}; |
||||
class ButtonHandler : public EventHandler |
||||
{ |
||||
public: |
||||
ButtonHandler (HIDPP20::Device *hidppdev, Device *d): |
||||
_irc4 (hidppdev), |
||||
dev (d), |
||||
states (0) {} |
||||
const HIDPP20::FeatureInterface *feature () const |
||||
{ |
||||
return &_irc4; |
||||
} |
||||
void handleEvent (const HIDPP::Report &event); |
||||
protected: |
||||
HIDPP20::IReprogControlsV4 _irc4; |
||||
Device* dev; |
||||
std::vector<uint16_t> states; |
||||
std::vector<uint16_t> new_states; |
||||
}; |
||||
|
||||
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) |
||||
{ |
||||
} |
||||
|
||||
virtual void start(); |
||||
virtual void stop(); |
||||
|
||||
protected: |
||||
virtual bool event (EventHandler* handler, const HIDPP::Report &report); |
||||
}; |
||||
|
||||
protected: |
||||
DeviceConfig* config; |
||||
bool DeviceRemoved; |
||||
EventListener* listener; |
||||
|
||||
void divert_buttons(bool scanning=false); |
||||
void set_smartshift(struct smartshift_options ops, bool scanning=false); |
||||
void set_hiresscroll(bool b, bool scanning=false); |
||||
void set_dpi(int dpi, bool scanning=false); |
||||
}; |
||||
|
||||
|
||||
#endif //LOGIOPS_DEVICE_H
|
@ -0,0 +1,143 @@ |
||||
#include <hid/DeviceMonitor.h> |
||||
#include <hidpp/SimpleDispatcher.h> |
||||
#include <hidpp/Device.h> |
||||
#include <hidpp10/Error.h> |
||||
#include <hidpp20/Error.h> |
||||
#include <cstring> |
||||
#include <unistd.h> |
||||
#include <future> |
||||
#include <fstream> |
||||
#include <sstream> |
||||
|
||||
#include "DeviceFinder.h" |
||||
#include "util.h" |
||||
#include "Device.h" |
||||
|
||||
void find_device() |
||||
{ |
||||
auto df = new DeviceFinder(); |
||||
df->run(); |
||||
} |
||||
|
||||
void DeviceFinder::addDevice(const char *path) |
||||
{ |
||||
const int max_tries = 5; |
||||
const int try_delay = 50000; |
||||
std::string string_path(path); |
||||
// Asynchronously scan device
|
||||
std::thread{[=]() |
||||
{ |
||||
// Check /sys/class/hidraw/hidrawX/device/uevent for device details.
|
||||
// Continue if HIDRAW_NAME contains 'Logitech' or is non-existent/
|
||||
// Otherwise, skip.
|
||||
std::string hidraw_name; |
||||
std::size_t spos = string_path.rfind('/'); |
||||
if(spos != std::string::npos) hidraw_name = string_path.substr(spos+1, string_path.size()-1); |
||||
else |
||||
{ |
||||
log_printf(ERROR, "Expected file but got directory: %s", path); |
||||
return; |
||||
} |
||||
std::ifstream info_file("/sys/class/hidraw/" + hidraw_name + "/device/uevent"); |
||||
std::map<std::string, std::string> hidraw_info; |
||||
std::string line; |
||||
while(std::getline(info_file, line)) |
||||
{ |
||||
if(line.find('=') == std::string::npos) continue; |
||||
hidraw_info.insert({line.substr(0, line.find('=')), line.substr(line.find('=') + 1, line.size()-1)}); |
||||
} |
||||
|
||||
if(hidraw_info.find("HID_NAME") != hidraw_info.end()) |
||||
if (hidraw_info.find("HID_NAME")->second.find("Logitech") == std::string::npos) return; |
||||
|
||||
//Check if device is an HID++ device and handle it accordingly
|
||||
try |
||||
{ |
||||
HIDPP::SimpleDispatcher dispatcher(string_path.c_str()); |
||||
bool has_receiver_index = false; |
||||
for(HIDPP::DeviceIndex index: { |
||||
HIDPP::DefaultDevice, HIDPP::CordedDevice, |
||||
HIDPP::WirelessDevice1, HIDPP::WirelessDevice2, |
||||
HIDPP::WirelessDevice3, HIDPP::WirelessDevice4, |
||||
HIDPP::WirelessDevice5, HIDPP::WirelessDevice6}) |
||||
{ |
||||
/// TODO: CONTINUOUSLY SCAN ALL DEVICES ON RECEIVER
|
||||
//Skip wireless devices if default device (receiver) has failed
|
||||
if(!has_receiver_index && index == HIDPP::WirelessDevice1) |
||||
break; |
||||
for(int i = 0; i < max_tries; i++) |
||||
{ |
||||
try |
||||
{ |
||||
HIDPP::Device d(&dispatcher, index); |
||||
auto version = d.protocolVersion(); |
||||
if(index == HIDPP::DefaultDevice && version == std::make_tuple(1, 0)) |
||||
has_receiver_index = true; |
||||
uint major, minor; |
||||
std::tie(major, minor) = d.protocolVersion(); |
||||
if(major > 1) // HID++ 2.0 devices only
|
||||
{ |
||||
auto dev = new Device(string_path.c_str(), index); |
||||
handlers.insert({ |
||||
dev, std::async(std::launch::async, &Device::start, *dev) |
||||
}); |
||||
log_printf(INFO, "%s detected: device %d on %s", d.name().c_str(), index, string_path.c_str()); |
||||
} |
||||
break; |
||||
} |
||||
catch(HIDPP10::Error &e) |
||||
{ |
||||
if(e.errorCode() != HIDPP10::Error::UnknownDevice) |
||||
{ |
||||
if(i == max_tries - 1) |
||||
log_printf(WARN, "Error while querying %s, wireless device %d: %s", string_path.c_str(), index, e.what()); |
||||
else usleep(try_delay); |
||||
} |
||||
} |
||||
catch(HIDPP20::Error &e) |
||||
{ |
||||
if(e.errorCode() != HIDPP20::Error::UnknownDevice) |
||||
{ |
||||
if(i == max_tries - 1) |
||||
log_printf(WARN, "Error while querying %s, device %d: %s", string_path.c_str(), index, e.what()); |
||||
else usleep(try_delay); |
||||
} |
||||
} |
||||
catch(HIDPP::Dispatcher::TimeoutError &e) |
||||
{ |
||||
if(i == max_tries - 1) |
||||
log_printf(WARN, "Device %s (index %d) timed out.", string_path.c_str(), index); |
||||
else usleep(try_delay); |
||||
} |
||||
catch(std::runtime_error &e) |
||||
{ |
||||
if(i == max_tries - 1) |
||||
log_printf(WARN, "Runtime error on device %d on %s: %s", index, string_path.c_str(), e.what()); |
||||
else usleep(try_delay); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
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) |
||||
{ |
||||
// Iterate through Devices, stop all in path
|
||||
auto it = handlers.begin(); |
||||
while (it != handlers.end()) |
||||
{ |
||||
if(it->first->path == path) |
||||
{ |
||||
log_printf(INFO, "%s on %s disconnected.", it->first->hidpp_dev->name(), path); |
||||
it->first->stop(); |
||||
it->second.wait(); |
||||
//handlers.erase(it);
|
||||
it++; |
||||
} |
||||
else |
||||
it++; |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
#ifndef MASTEROPTIONS_DEVICEFINDER_H |
||||
#define MASTEROPTIONS_DEVICEFINDER_H |
||||
|
||||
#include "Device.h" |
||||
|
||||
struct handler_pair; |
||||
|
||||
class DeviceFinder : public HID::DeviceMonitor |
||||
{ |
||||
protected: |
||||
void addDevice(const char* path); |
||||
void removeDevice(const char* path); |
||||
std::map<Device*, std::future<void>> handlers; |
||||
}; |
||||
|
||||
void find_device(); |
||||
|
||||
#endif //MASTEROPTIONS_DEVICEFINDER_H
|
@ -0,0 +1,35 @@ |
||||
#include <libevdev/libevdev.h> |
||||
#include <libevdev/libevdev-uinput.h> |
||||
#include <system_error> |
||||
|
||||
#include "EvdevDevice.h" |
||||
|
||||
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::send_event(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); |
||||
} |
@ -0,0 +1,19 @@ |
||||
#ifndef MASTEROPTIONS_EVDEVDEVICE_H |
||||
#define MASTEROPTIONS_EVDEVDEVICE_H |
||||
|
||||
#include <libevdev/libevdev.h> |
||||
#include <libevdev/libevdev-uinput.h> |
||||
|
||||
class EvdevDevice |
||||
{ |
||||
public: |
||||
EvdevDevice(const char* name); |
||||
~EvdevDevice(); |
||||
void send_event(unsigned int type, unsigned int code, int value); |
||||
libevdev* device; |
||||
libevdev_uinput* ui_device; |
||||
}; |
||||
|
||||
extern EvdevDevice* global_evdev; |
||||
|
||||
#endif //MASTEROPTIONS_EVDEVDEVICE_H
|
@ -0,0 +1,42 @@ |
||||
#include <hidpp/SimpleDispatcher.h> |
||||
#include <hidpp/DispatcherThread.h> |
||||
#include <hidpp20/Device.h> |
||||
#include <hidpp20/Error.h> |
||||
#include <hidpp20/IReprogControlsV4.h> |
||||
#include <hidpp20/UnsupportedFeature.h> |
||||
#include <hid/DeviceMonitor.h> |
||||
#include <algorithm> |
||||
#include <cstdio> |
||||
#include <cstdlib> |
||||
|
||||
#include "util.h" |
||||
#include "Device.h" |
||||
#include "Actions.h" |
||||
#include "Configuration.h" |
||||
#include "EvdevDevice.h" |
||||
#include "DeviceFinder.h" |
||||
|
||||
#define evdev_name "logid" |
||||
|
||||
LogLevel global_verbosity = DEBUG; |
||||
Configuration* global_config; |
||||
EvdevDevice* global_evdev; |
||||
|
||||
int main(int argc, char** argv) |
||||
{ |
||||
// Read config
|
||||
try { global_config = new Configuration("logid.cfg"); } |
||||
catch (std::exception &e) { return EXIT_FAILURE; } |
||||
|
||||
//Create an evdev device called 'logid'
|
||||
try { global_evdev = new EvdevDevice(evdev_name); } |
||||
catch(std::system_error& e) |
||||
{ |
||||
log_printf(ERROR, "Could not create evdev device: %s", e.what()); |
||||
return EXIT_FAILURE; |
||||
} |
||||
|
||||
find_device(); // Scan devices, create listeners, handlers, etc.
|
||||
|
||||
return EXIT_SUCCESS; |
||||
} |
@ -0,0 +1,109 @@ |
||||
#include <cstdio> |
||||
#include <string> |
||||
#include <cstring> |
||||
#include <cstdarg> |
||||
#include <cmath> |
||||
#include <algorithm> |
||||
|
||||
#include "util.h" |
||||
#include "Actions.h" |
||||
|
||||
void log_printf(LogLevel level, const char* format, ...) |
||||
{ |
||||
if(global_verbosity > level) return; |
||||
|
||||
va_list vargs; |
||||
va_start(vargs, format); |
||||
|
||||
FILE* stream = stdout; |
||||
if(level == ERROR || level == WARN) stream = stderr; |
||||
|
||||
fprintf(stream, "[%s] ", level_prefix(level)); |
||||
vfprintf(stream, format, vargs); |
||||
fprintf(stream, "\n"); |
||||
} |
||||
|
||||
const char* level_prefix(LogLevel level) |
||||
{ |
||||
if(level == DEBUG) return "DEBUG"; |
||||
if(level == INFO) return "INFO" ; |
||||
if(level == WARN) return "WARN"; |
||||
if(level == ERROR) return "ERROR"; |
||||
|
||||
return "DEBUG"; |
||||
} |
||||
|
||||
Direction get_direction(int x, int y) |
||||
{ |
||||
if(abs(x) < 50 && abs(y) < 50) return Direction::None; |
||||
|
||||
double angle; |
||||
|
||||
if(x == 0 && y > 0) angle = 90; // Y+
|
||||
else if(x == 0 && y < 0) angle = 270; // Y-
|
||||
else if(x > 0 && y == 0) angle = 0; // X+
|
||||
else if(x < 0 && y == 0) angle = 180; // X+
|
||||
else |
||||
{ |
||||
angle = fabs(atan((double)y/(double)x) * 180.0 / M_PI); |
||||
|
||||
if(x < 0 && y > 0) angle = 180.0 - angle; //Q2
|
||||
else if(x < 0 && y < 0) angle += 180; // Q3
|
||||
else if(x > 0 && y < 0) angle = 360.0 - angle; // Q4
|
||||
} |
||||
|
||||
if(315 < angle || angle <= 45) return Direction::Right; |
||||
else if(45 < angle && angle <= 135) return Direction::Down; |
||||
else if(135 < angle && angle <= 225) return Direction::Left; |
||||
else if(225 < angle && angle <= 315) return Direction::Up; |
||||
|
||||
return Direction::None; |
||||
} |
||||
|
||||
Direction string_to_direction(std::string s) |
||||
{ |
||||
const char* original_str = s.c_str(); |
||||
std::transform(s.begin(), s.end(), s.begin(), ::tolower); |
||||
|
||||
if(s == "none") return Direction::None; |
||||
if(s == "up") return Direction::Up; |
||||
if(s == "down") return Direction::Down; |
||||
if(s == "left") return Direction::Left; |
||||
if(s == "right") return Direction::Right; |
||||
|
||||
s = original_str; |
||||
|
||||
throw std::invalid_argument(s + " is an invalid direction."); |
||||
} |
||||
|
||||
GestureMode string_to_gesturemode(std::string s) |
||||
{ |
||||
const char* original_str = s.c_str(); |
||||
std::transform(s.begin(), s.end(), s.begin(), ::tolower); |
||||
|
||||
if(s == "nopress") return GestureMode::NoPress; |
||||
if(s == "onrelease") return GestureMode::OnRelease; |
||||
if(s == "onfewpixels") return GestureMode::OnFewPixels; |
||||
|
||||
s = original_str; |
||||
|
||||
log_printf(INFO, "%s is an invalid gesture mode. Defaulting to OnRelease", original_str); |
||||
|
||||
return GestureMode::OnRelease; |
||||
} |
||||
|
||||
Action string_to_action(std::string s) |
||||
{ |
||||
std::string original_str = s; |
||||
std::transform(s.begin(), s.end(), s.begin(), ::tolower); |
||||
|
||||
if(s == "none") return Action::None; |
||||
if(s == "keypress") return Action::Keypress; |
||||
if(s == "gestures") return Action::Gestures; |
||||
if(s == "togglesmartshift") return Action::ToggleSmartshift; |
||||
if(s == "togglehiresscroll") return Action::ToggleHiresScroll; |
||||
if(s == "cycledpi") return Action::CycleDPI; |
||||
if(s == "changedpi") return Action::ChangeDPI; |
||||
|
||||
throw std::invalid_argument(original_str + " is an invalid action."); |
||||
} |
@ -0,0 +1,25 @@ |
||||
#ifndef MASTEROPTIONS_LOGGER_H |
||||
#define MASTEROPTIONS_LOGGER_H |
||||
|
||||
#include "Actions.h" |
||||
|
||||
enum LogLevel |
||||
{ |
||||
DEBUG, |
||||
INFO, |
||||
WARN, |
||||
ERROR |
||||
}; |
||||
|
||||
extern LogLevel global_verbosity; |
||||
|
||||
void log_printf(LogLevel level, const char* format, ...); |
||||
|
||||
const char* level_prefix(LogLevel level); |
||||
|
||||
Direction get_direction(int x, int y); |
||||
Direction string_to_direction(std::string s); |
||||
GestureMode string_to_gesturemode(std::string s); |
||||
Action string_to_action(std::string s); |
||||
|
||||
#endif //MASTEROPTIONS_LOGGER_H
|
Loading…
Reference in new issue