Commit 8339d844 authored by sebt3's avatar sebt3
Browse files

Initial stuff from B-ZaR code

parents
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
project(funkeymonkey-pyrainput)
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
include_directories(include)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_library(pyrainput SHARED pyrainput.cpp)
install(TARGETS pyrainput DESTINATION lib/funkeymonkey)
Copyright (c) 2017, Sebastien Huss
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# FunKeyMonkey-pyrainput
A pyra input deamon based on FunKezy-MonKey
### Install
Standard CMake stuff:
```
mkdir build
cd build
cmake ..
make
```
funkeymonkey-pyrainput for Debian
A module for FunKey-MonKey that handle the Pyra input system
-- Sébastien Huss <sebastien.huss@gmail.com> Sat, 26 Aug 2017 14:17:20 +0200
funkeymonkey-pyrainput (0.1.0-1) UNRELEASED; urgency=low
* Initial release.
-- Sébastien Huss <sebastien.huss@gmail.com> Sat, 26 Aug 2017 14:17:20 +0200
Source: funkeymonkey-pyrainput
Section: unknown
Priority: extra
Maintainer: Sébastien Huss <sebastien.huss@gmail.com>
Build-Depends: cmake, debhelper (>=9), funkeymonkey-dev
Standards-Version: 3.9.8
Homepage: https://github.com/sebt3/funkeymonkey-pyrainput
Package: funkeymonkey-pyrainput
Architecture: any
Multi-Arch: foreign
Depends: ${misc:Depends}, ${shlibs:Depends}
Description: A module for FunKey-MonKey that handle the Pyra input system
A module for FunKey-MonKey that handle the Pyra input system
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: funkeymonkey-pyrainput
Source: https://github.com/sebt3/funkeymonkey-pyrainput
#
# Please double check copyright with the licensecheck(1) command.
Files: CMakeLists.txt
README.md
pyrainput.cpp
Copyright: __NO_COPYRIGHT_NOR_LICENSE__
License: __NO_COPYRIGHT_NOR_LICENSE__
#----------------------------------------------------------------------------
# Files marked as NO_LICENSE_TEXT_FOUND may be covered by the following
# license/copyright files.
#----------------------------------------------------------------------------
# License file: LICENSE
Copyright (c) 2017, Sebastien Huss
All rights reserved.
.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
.
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# You must remove unused comment lines for the released package.
#!/usr/bin/make -f
# You must remove unused comment lines for the released package.
#export DH_VERBOSE = 1
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
%:
dh $@
#override_dh_auto_configure:
# dh_auto_configure -- \
# -DCMAKE_LIBRARY_ARCHITECTURE="$(DEB_TARGET_MULTIARCH)"
#abort-on-upstream-changes
#unapply-patches
# You must remove unused comment lines for the released package.
version=3
#include <funkeymonkey/funkeymonkeymodule.h>
#include <funkeymonkey/uinputdevice.h>
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <regex>
#include <fstream>
#include <algorithm>
#include <cctype>
#include <unordered_map>
#include <functional>
#include <array>
static constexpr unsigned int FIRST_KEY = KEY_RESERVED;
static constexpr unsigned int LAST_KEY = KEY_UNKNOWN;
template<int FIRST_KEY, int LAST_KEY>
class KeyBehaviors
{
public:
void passthrough(unsigned int code);
void map(unsigned int code, unsigned int result);
void altmap(unsigned int code, bool* flag,
unsigned int regular,
unsigned int alternative);
void complex(unsigned int code, std::function<void(int)> function);
void handle(unsigned int code, int value);
private:
struct KeyBehavior
{
KeyBehavior() : type(PASSTHROUGH), mapping(0), function() {}
enum Type { PASSTHROUGH, MAPPED, ALTMAPPED, COMPLEX };
Type type;
int mapping;
int alternative;
bool* flag;
std::function<void(int)> function;
};
static constexpr unsigned int NUM_KEYS = LAST_KEY - FIRST_KEY + 1;
std::array<KeyBehavior, NUM_KEYS> behaviors;
};
KeyBehaviors<FIRST_KEY, LAST_KEY>* behaviors;
UinputDevice* out;
struct Mouse
{
// Any thread using device must hold mutex
UinputDevice device;
// May be changed from any thread at any time
int dx;
int dy;
int dwx;
int dwy;
// Used to signal changes in above values, uses mutex
std::condition_variable signal;
// Mouse device mutex
std::mutex mutex;
};
struct Settings
{
enum NubAxisMode {
UNKNOWN_NUB_AXIS_MODE,
LEFT_JOYSTICK_X, LEFT_JOYSTICK_Y,
RIGHT_JOYSTICK_X, RIGHT_JOYSTICK_Y,
MOUSE_X, MOUSE_Y,
SCROLL_X, SCROLL_Y
};
enum NubClickMode {
UNKNOWN_NUB_CLICK_MODE,
NUB_CLICK_LEFT, NUB_CLICK_RIGHT,
MOUSE_LEFT, MOUSE_RIGHT
};
NubAxisMode leftNubModeX = LEFT_JOYSTICK_X;
NubAxisMode leftNubModeY = LEFT_JOYSTICK_Y;
NubAxisMode rightNubModeX = RIGHT_JOYSTICK_X;
NubAxisMode rightNubModeY = RIGHT_JOYSTICK_Y;
NubClickMode leftNubClickMode = NUB_CLICK_LEFT;
NubClickMode rightNubClickMode = NUB_CLICK_RIGHT;
// May be changed from any thread at any time
int mouseDeadzone = 100;
int mouseSensitivity = 15;
int mouseWheelDeadzone = 500;
std::string configFile;
};
using NubAxisModeMap = std::unordered_map<std::string, Settings::NubAxisMode>;
NubAxisModeMap const NUB_AXIS_MODES = {
{ "left_joystick_x", Settings::LEFT_JOYSTICK_X },
{ "left_joystick_y", Settings::LEFT_JOYSTICK_Y },
{ "right_joystick_x", Settings::RIGHT_JOYSTICK_X },
{ "right_joystick_y", Settings::RIGHT_JOYSTICK_Y },
{ "mouse_x", Settings::MOUSE_X },
{ "mouse_y", Settings::MOUSE_Y },
{ "scroll_x", Settings::SCROLL_X },
{ "scroll_y", Settings::SCROLL_Y }
};
using NubClickModeMap = std::unordered_map<std::string, Settings::NubClickMode>;
NubClickModeMap const NUB_CLICK_MODES = {
{ "nub_click_left", Settings::NUB_CLICK_LEFT },
{ "nub_click_right", Settings::NUB_CLICK_RIGHT },
{ "mouse_left", Settings::MOUSE_LEFT },
{ "mouse_right", Settings::MOUSE_RIGHT }
};
void handleArgs(char const** argv, unsigned int argc, Settings& settings);
void loadConfig(std::string const& filename, Settings& settings);
Settings::NubAxisMode parseNubAxisMode(std::string const& str);
Settings::NubClickMode parseNubClickMode(std::string const& str);
void handleNubAxis(Settings::NubAxisMode mode, int value, Mouse* mouse, UinputDevice* gamepad, Settings const& settings);
void handleNubClick(Settings::NubClickMode mode, int value, Mouse* mouse, UinputDevice* gamepad, Settings const& settings);
// Mouse movement/scroll thread handler
void handleMouse(Mouse* mouse, Settings* settings, bool* stop);
struct
{
bool stop = false;
UinputDevice* gamepad = nullptr;
Mouse* mouse = nullptr;
std::thread mouseThread;
Settings settings;
} global;
void init(char const** argv, unsigned int argc)
{
std::vector<unsigned int> keycodes;
for(unsigned int i = FIRST_KEY; i <= LAST_KEY; ++i)
{
keycodes.push_back(i);
}
out = new UinputDevice("/dev/uinput", BUS_USB, "FunKeyMonkey keyboard", 1, 1, 1, {
{ EV_KEY, keycodes }
});
behaviors = new KeyBehaviors<FIRST_KEY, LAST_KEY>();
// Example mappings
behaviors->map(KEY_K, KEY_L);
behaviors->complex(KEY_O, [](int value) {
out->send(EV_KEY, KEY_I, value);
out->send(EV_KEY, KEY_O, value);
});
global.gamepad = new UinputDevice("/dev/uinput", BUS_USB,
"Modal Gamepad", 1, 1, 1, {
{ EV_KEY, {
BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST,
BTN_TL, BTN_TR, BTN_TL2, BTN_TR2,
BTN_SELECT, BTN_START, BTN_MODE,
BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
BTN_THUMBL, BTN_THUMBR
} },
{ EV_ABS, { REL_X, REL_Y, REL_RX, REL_RY } }
});
global.mouse = new Mouse {
UinputDevice("/dev/uinput", BUS_USB, "Modal Gamepad Mouse", 1, 1, 1, {
{ EV_KEY, { BTN_LEFT, BTN_RIGHT } },
{ EV_REL, { REL_X, REL_Y, REL_HWHEEL, REL_WHEEL } }
}), 0, 0, 0, 0, {}, {}
};
global.mouseThread = std::move(std::thread(handleMouse,
global.mouse, &global.settings, &global.stop));
handleArgs(argv, argc, global.settings);
if(!global.settings.configFile.empty())
{
loadConfig(global.settings.configFile, global.settings);
}
}
void handle(input_event const& e)
{
switch(e.type)
{
case EV_ABS:
switch(e.code)
{
case ABS_X:
handleNubAxis(global.settings.leftNubModeX, e.value,
global.mouse, global.gamepad, global.settings);
break;
case ABS_Y:
handleNubAxis(global.settings.leftNubModeY, e.value,
global.mouse, global.gamepad, global.settings);
break;
case ABS_RX:
handleNubAxis(global.settings.rightNubModeX, e.value,
global.mouse, global.gamepad, global.settings);
break;
case ABS_RY:
handleNubAxis(global.settings.rightNubModeY, e.value,
global.mouse, global.gamepad, global.settings);
break;
default: break;
};
break;
case EV_KEY:
behaviors->handle(e.code, e.value);
out->send(EV_SYN, 0, 0);
switch(e.code)
{
case BTN_THUMBL:
handleNubClick(global.settings.leftNubClickMode, e.value,
global.mouse, global.gamepad, global.settings);
break;
case BTN_THUMBR:
handleNubClick(global.settings.rightNubClickMode, e.value,
global.mouse, global.gamepad, global.settings);
break;
default: break;
}
}
}
void destroy()
{
global.stop = true;
global.mouse->signal.notify_all();
if(global.gamepad)
{
delete global.gamepad;
}
if(out)
{
delete out;
}
if(behaviors)
{
delete behaviors;
}
global.mouseThread.join();
}
void user1()
{
loadConfig(global.settings.configFile, global.settings);
}
void user2()
{
}
void handleArgs(char const** argv, unsigned int argc, Settings& settings)
{
std::string configFile;
std::regex configRegex("config=(.*)");
for(unsigned int i = 0; i < argc; ++i)
{
std::string arg(argv[i]);
std::smatch match;
if(std::regex_match(arg, match, configRegex))
{
settings.configFile = match[1];
}
}
}
using SettingHandler = std::function<void(std::string const&,Settings&)>;
using SettingHandlerMap = std::unordered_map<std::string, SettingHandler>;
SettingHandlerMap const SETTING_HANDLERS = {
{ "mouse.sensitivity", [](std::string const& value, Settings& settings)
{
settings.mouseSensitivity = std::stoi(value);
}
},
{ "mouse.deadzone", [](std::string const& value, Settings& settings)
{
settings.mouseDeadzone = std::stoi(value);
}
},
{ "mouse.wheel.deadzone", [](std::string const& value, Settings& settings)
{
settings.mouseWheelDeadzone = std::stoi(value);
}
},
{ "nubs.left.x", [](std::string const& value, Settings& settings)
{
settings.leftNubModeX = parseNubAxisMode(value);
}
},
{ "nubs.left.y", [](std::string const& value, Settings& settings)
{
settings.leftNubModeY = parseNubAxisMode(value);
}
},
{ "nubs.right.x", [](std::string const& value, Settings& settings)
{
settings.rightNubModeX = parseNubAxisMode(value);
}
},
{ "nubs.right.y", [](std::string const& value, Settings& settings)
{
settings.rightNubModeY = parseNubAxisMode(value);
}
},
{ "nubs.left.click", [](std::string const& value, Settings& settings)
{
settings.leftNubClickMode = parseNubClickMode(value);
}
},
{ "nubs.right.click", [](std::string const& value, Settings& settings)
{
settings.rightNubClickMode = parseNubClickMode(value);
}
}
};
void loadConfig(std::string const& filename, Settings& settings)
{
std::regex re("^([\\w.]+)\\s*=\\s*(.*)$");
std::regex emptyRe("^\\s*$");
std::ifstream configFile(filename);
if(!configFile)
{
std::cerr << "ERROR: Could not open config file " << filename << std::endl;
return;
}
std::string line;
while(std::getline(configFile, line))
{
if(line.empty() || line.at(0) == '#' || std::regex_match(line, emptyRe))
continue;
std::smatch match;
if(std::regex_match(line, match, re))
{
std::string key = match[1];
std::string value = match[2];
std::transform(key.begin(), key.end(), key.begin(), tolower);
auto iter = SETTING_HANDLERS.find(key);
if(iter == SETTING_HANDLERS.end())
{
std::cout << "WARNING: Unknown setting in config file: "
<< key << std::endl;
}
else
{
iter->second(value, settings);
}
}
else
{
std::cerr << "Invalid line in config file: " << line << std::endl;
}
}
}
Settings::NubAxisMode parseNubAxisMode(std::string const& str)
{
std::string s(str);
std::transform(s.begin(), s.end(), s.begin(), tolower);
auto iter = NUB_AXIS_MODES.find(s);
if(iter == NUB_AXIS_MODES.end())
{
return Settings::UNKNOWN_NUB_AXIS_MODE;
}
else
{
return iter->second;
}
}
Settings::NubClickMode parseNubClickMode(std::string const& str)
{
std::string s(str);
std::transform(s.begin(), s.end(), s.begin(), tolower);
auto iter = NUB_CLICK_MODES.find(s);
if(iter == NUB_CLICK_MODES.end())
{
return Settings::UNKNOWN_NUB_CLICK_MODE;
}
else
{
return iter->second;
}
}
void handleNubAxis(Settings::NubAxisMode mode, int value, Mouse* mouse, UinputDevice* gamepad, Settings const& settings)
{
switch(mode)
{
case Settings::MOUSE_X:
mouse->dx = value;
if(mouse->dx > settings.mouseDeadzone
|| mouse->dx < -settings.mouseDeadzone)
mouse->signal.notify_all();
break;
case Settings::MOUSE_Y:
mouse->dy = value;
if(mouse->dy > settings.mouseDeadzone
|| mouse->dy < -settings.mouseDeadzone)
mouse->signal.notify_all();
break;
case Settings::SCROLL_X:
mouse->dwx = value;
if(mouse->dwx > settings.mouseDeadzone
|| mouse->dwx < -settings.mouseDeadzone)
mouse->signal.notify_all();
break;
case Settings::SCROLL_Y:
mouse->dwy = value;
if(mouse->dwy > settings.mouseDeadzone
|| mouse->dwy < -settings.mouseDeadzone)
mouse->signal.notify_all();
break;
case Settings::LEFT_JOYSTICK_X:
gamepad->send(EV_ABS, ABS_X, value);
gamepad->send(EV_SYN, 0, 0);
break;
case Settings::LEFT_JOYSTICK_Y:
gamepad->send(EV_ABS, ABS_Y, value);
gamepad->send(EV_SYN, 0, 0);
break;
case Settings::RIGHT_JOYSTICK_X:
gamepad->send(EV_ABS, ABS_RX, value);
gamepad->send(EV_SYN, 0, 0);
break;
case Settings::RIGHT_JOYSTICK_Y:
gamepad->send(EV_ABS, ABS_RY, value);
gamepad->send(EV_SYN, 0, 0);
break;
case Settings::UNKNOWN_NUB_AXIS_MODE:
break;
}
}
void handleNubClick(Settings::NubClickMode mode, int value, Mouse* mouse, UinputDevice* gamepad, Settings const& settings)
{
switch(mode)
{
case Settings::MOUSE_LEFT:
{
std::lock_guard<std::mutex> lk(mouse->mutex);
mouse->device.send(EV_KEY, BTN_LEFT, value);
mouse->device.send(EV_SYN, 0, 0);
break;
}
case Settings::MOUSE_RIGHT:
{
std::lock_guard<std::mutex> lk(mouse->mutex);
mouse->device.send(EV_KEY, BTN_RIGHT, value);
mouse->device.send(EV_SYN, 0, 0);
break;
}
case Settings::NUB_CLICK_LEFT:
gamepad->send(EV_KEY, BTN_THUMBL, value);
gamepad->send(EV_SYN, 0, 0);