Major refactoring, transferring from masterd
This commit is contained in:
parent
2190b527a7
commit
9c092edcf3
14
CMakeLists.txt
Normal file
14
CMakeLists.txt
Normal 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
32
README.md
Normal 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
71
logid.example.cfg
Normal 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
154
src/logid/Actions.cpp
Normal 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
122
src/logid/Actions.h
Normal 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
41
src/logid/CMakeLists.txt
Normal 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
386
src/logid/Configuration.cpp
Normal 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
41
src/logid/Configuration.h
Normal 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
292
src/logid/Device.cpp
Normal 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
109
src/logid/Device.h
Normal 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
143
src/logid/DeviceFinder.cpp
Normal 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
18
src/logid/DeviceFinder.h
Normal 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
35
src/logid/EvdevDevice.cpp
Normal 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
19
src/logid/EvdevDevice.h
Normal 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
42
src/logid/logid.cpp
Normal 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
109
src/logid/util.cpp
Normal 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
25
src/logid/util.h
Normal 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
|
Loading…
Reference in New Issue
Block a user