Major refactoring, transferring from masterd

This commit is contained in:
PixlOne 2019-07-17 01:53:51 -04:00
parent 2190b527a7
commit 9c092edcf3
17 changed files with 1653 additions and 0 deletions

14
CMakeLists.txt Normal file
View File

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

32
README.md Normal file
View File

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

71
logid.example.cfg Normal file
View File

@ -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"];
};
}
);
}
);

154
src/logid/Actions.cpp Normal file
View File

@ -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());
}
}

122
src/logid/Actions.h Normal file
View File

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

41
src/logid/CMakeLists.txt Normal file
View File

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

386
src/logid/Configuration.cpp Normal file
View File

@ -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 = {};
}

41
src/logid/Configuration.h Normal file
View File

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

292
src/logid/Device.cpp Normal file
View File

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

109
src/logid/Device.h Normal file
View File

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

143
src/logid/DeviceFinder.cpp Normal file
View File

@ -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++;
}
}

18
src/logid/DeviceFinder.h Normal file
View File

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

35
src/logid/EvdevDevice.cpp Normal file
View File

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

19
src/logid/EvdevDevice.h Normal file
View File

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

42
src/logid/logid.cpp Normal file
View File

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

109
src/logid/util.cpp Normal file
View File

@ -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.");
}

25
src/logid/util.h Normal file
View File

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