Repository: tolga9009/sidewinderd
Branch: master
Commit: 638bc839d74d
Files: 38
Total size: 65.7 KB
Directory structure:
gitextract_np3q0_ka/
├── .gitignore
├── CMakeLists.txt
├── CONTRIBUTING
├── LICENSE
├── README.md
├── cmake/
│ ├── FindConfig++.cmake
│ ├── FindTinyXML2.cmake
│ └── FindUDev.cmake
├── etc/
│ ├── sidewinderd.conf
│ └── sidewinderd.service.in
└── src/
├── CMakeLists.txt
├── core/
│ ├── device.hpp
│ ├── device_manager.cpp
│ ├── device_manager.hpp
│ ├── hid_interface.cpp
│ ├── hid_interface.hpp
│ ├── key.cpp
│ ├── key.hpp
│ ├── keyboard.cpp
│ ├── keyboard.hpp
│ ├── led.cpp
│ ├── led.hpp
│ ├── led_group.cpp
│ ├── led_group.hpp
│ ├── virtual_input.cpp
│ └── virtual_input.hpp
├── device_data.hpp
├── main.cpp
├── process.cpp
├── process.hpp
└── vendor/
├── logitech/
│ ├── g103.cpp
│ ├── g103.hpp
│ ├── g105.cpp
│ ├── g105.hpp
│ ├── g710.cpp
│ └── g710.hpp
└── microsoft/
├── sidewinder.cpp
└── sidewinder.hpp
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
build
CMakeLists.txt.user
================================================
FILE: CMakeLists.txt
================================================
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8)
PROJECT(sidewinderd)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra")
ADD_SUBDIRECTORY(src)
SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
FIND_PACKAGE(Config++ REQUIRED)
FIND_PACKAGE(TinyXML2 REQUIRED)
FIND_PACKAGE(UDev REQUIRED)
================================================
FILE: CONTRIBUTING
================================================
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
================================================
FILE: LICENSE
================================================
Sidewinder daemon is made available under the MIT License.
Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Sidewinder daemon
This project provides support for gaming peripherals under Linux. It was
originally designed for the Microsoft SideWinder X4, but we have extended
support for more keyboards. Our goal is to create a framework-like environment
for rapid driver development under Linux.
## Supported devices
* Microsoft SideWinder X4
* Microsoft SideWinder X6
* Logitech G103
* Logitech G105
* Logitech G710
* Logitech G710+
## Install
Please check, if this project has already been packaged for your specific Linux
distribution. Currently maintained packages:
* Arch Linux: https://aur.archlinux.org/packages/sidewinderd/
If there are no packages available for your Linux distribution, please proceed
with the manual installation from source:
1. Install the following dependencies. Please refer to your specific Linux
distribution, as package names might differ.
* cmake 2.8.8 (make)
* libconfig 1.4.9
* tinyxml2 2.2.0
* libudev 210
2. Create a build directory in the toplevel directory:
```
mkdir build
```
3. Run cmake from within the new build directory:
```
cd build
cmake ..
```
4. Compile and install:
```
make
make install
```
## Usage
Enable and start Sidewinder daemon:
systemctl enable sidewinderd.service
systemctl start sidewinderd.service
Configure `/etc/sidewinderd.conf` according to your needs. Please change the
user, as the default user is root.
You can now use your gaming peripheral! Please note, that there is no graphical
user interface. Some LEDs might light up, letting you know, that Sidewinder
daemon has successfully recognized your keyboard.
## Record macros
The macro keys are fully programmable, but there is no default event. You can
add functions to them, by recording macros or key combinations:
1. Choose a profile. The profile LED on the device will show you, which profile
is active.
2. Press record key. The record LED will light up.
3. Now, choose and press a macro key. On some devices, the record LED will begin
to blink. You're now in macro mode. Please note, that existing macros may get
overwritten.
4. Everything you type on the keyboard will get recorded. You can either record
a single key combination or a long series of keypresses. Please note, that
keypresses still send events to your operating system and your programs.
5. When done, press the record key again. This will stop recording and save the
macro.
6. You've now created a macro. Use it by setting the chosen profile and pressing
the chosen macro key.
## Contribution
In order to contribute to this project, you need to read and agree the Developer
Certificate of Origin, which can be found in the CONTRIBUTING file. Therefore,
commits need to be signed-off. You can do this by adding the `-s` flag when
commiting: `git commit -s`. Pseudonyms and anonymous contributions will not be
accepted.
## License
This project is made available under the MIT License. For more information,
please refer to the LICENSE file.
================================================
FILE: cmake/FindConfig++.cmake
================================================
FIND_PATH(CONFIG++_INCLUDE_DIR libconfig.h++ /usr/include /usr/local/include)
FIND_LIBRARY(CONFIG++_LIBRARY NAMES config++ PATH /usr/lib /usr/local/lib)
IF(CONFIG++_INCLUDE_DIR AND CONFIG++_LIBRARY)
SET(CONFIG++_FOUND TRUE)
ENDIF(CONFIG++_INCLUDE_DIR AND CONFIG++_LIBRARY)
IF(CONFIG++_FOUND)
IF(NOT CONFIG++_FIND_QUIETLY)
MESSAGE(STATUS "Found Config++: ${CONFIG++_LIBRARY}")
ENDIF(NOT CONFIG++_FIND_QUIETLY)
ELSE(CONFIG++_FOUND)
IF(CONFIG++_FIND_REQUIRED)
IF(NOT CONFIG++_INCLUDE_DIR)
MESSAGE(FATAL_ERROR "Could not find LibConfig++ header file!")
ENDIF(NOT CONFIG++_INCLUDE_DIR)
IF(NOT CONFIG++_LIBRARY)
MESSAGE(FATAL_ERROR "Could not find LibConfig++ library file!")
ENDIF(NOT CONFIG++_LIBRARY)
ENDIF(CONFIG++_FIND_REQUIRED)
ENDIF(CONFIG++_FOUND)
================================================
FILE: cmake/FindTinyXML2.cmake
================================================
FIND_PATH(TINYXML2_INCLUDE_DIR tinyxml2.h /usr/include /usr/local/include)
FIND_LIBRARY(TINYXML2_LIBRARY NAMES tinyxml2 PATH /usr/lib /usr/local/lib)
IF(TINYXML2_INCLUDE_DIR AND TINYXML2_LIBRARY)
SET(TINYXML2_FOUND TRUE)
ENDIF(TINYXML2_INCLUDE_DIR AND TINYXML2_LIBRARY)
IF(TINYXML2_FOUND)
IF(NOT TINYXML2_FIND_QUIETLY)
MESSAGE(STATUS "Found TinyXML2: ${TINYXML2_LIBRARY}")
ENDIF(NOT TINYXML2_FIND_QUIETLY)
ELSE(TINYXML2_FOUND)
IF(TINYXML2_FIND_REQUIRED)
IF(NOT TINYXML2_INCLUDE_DIR)
MESSAGE(FATAL_ERROR "Could not find TinyXML2 header file!")
ENDIF(NOT TINYXML2_INCLUDE_DIR)
IF(NOT TINYXML2_LIBRARY)
MESSAGE(FATAL_ERROR "Could not find TinyXML2 library files!")
ENDIF(NOT TINYXML2_LIBRARY)
ENDIF(TINYXML2_FIND_REQUIRED)
ENDIF(TINYXML2_FOUND)
================================================
FILE: cmake/FindUDev.cmake
================================================
FIND_PATH(UDEV_INCLUDE_DIR libudev.h /usr/include /usr/local/include)
FIND_LIBRARY(UDEV_LIBRARY NAMES udev libudev PATH /usr/lib /usr/local/lib)
IF(UDEV_INCLUDE_DIR AND UDEV_LIBRARY)
SET(UDEV_FOUND TRUE)
ENDIF(UDEV_INCLUDE_DIR AND UDEV_LIBRARY)
IF(UDEV_FOUND)
IF(NOT UDEV_FIND_QUIETLY)
MESSAGE(STATUS "Found UDev: ${UDEV_LIBRARY}")
ENDIF(NOT UDEV_FIND_QUIETLY)
ELSE(UDEV_FOUND)
IF(UDEV_FIND_REQUIRED)
IF(NOT UDEV_INCLUDE_DIR)
MESSAGE(FATAL_ERROR "Could not find UDev header file!")
ENDIF(NOT UDEV_INCLUDE_DIR)
IF(NOT UDEV_LIBRARY)
MESSAGE(FATAL_ERROR "Could not find UDev library files!")
ENDIF(NOT UDEV_LIBRARY)
ENDIF(UDEV_FIND_REQUIRED)
ENDIF(UDEV_FOUND)
================================================
FILE: etc/sidewinderd.conf
================================================
# Configuration file for sidewinderd
# sidewinderd will run as the user specified here. It is not recommended to
# leave it at default value "root".
user = "root";
# If set to false, macro recording will not capture any delays.
capture_delays = true;
# Change the PID file path here, if you experience issues with the default path.
pid-file = "/var/run/sidewinderd.pid";
# Defines, whether sidewinderd should wait for the working directory to become
# available or not.
# If set to true, sidewinderd will infinitely poll and wait, until the working
# directory becomes available. This is needed in environments, where the
# specified user's home directory is encrypted and not available on boot.
#encrypted_workdir = false;
# You can set an alternative profile path here.
# If this setting is set, sidewinderd will no longer use your specified user's
# home directory for storing application data, but instead use this path.
#workdir = "/var/lib/sidewinderd";
================================================
FILE: etc/sidewinderd.service.in
================================================
[Unit]
Description=Support for Microsoft SideWinder X4 / X6 and Logitech G105 / G710+
After=multi-user.target
[Service]
ExecStart=${CMAKE_INSTALL_PREFIX}/bin/sidewinderd
[Install]
WantedBy=multi-user.target
================================================
FILE: src/CMakeLists.txt
================================================
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} ROOT_SRC)
AUX_SOURCE_DIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/core" CORE_SRC)
AUX_SOURCE_DIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/vendor/logitech" LOGITECH_SRC)
AUX_SOURCE_DIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/vendor/microsoft" MICROSOFT_SRC)
LIST(APPEND VENDOR_LIST ${LOGITECH_SRC} ${MICROSOFT_SRC})
LIST(APPEND SOURCE_LIST ${ROOT_SRC} ${CORE_SRC} ${VENDOR_LIST})
CONFIGURE_FILE("${PROJECT_SOURCE_DIR}/etc/sidewinderd.service.in" "${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service")
ADD_EXECUTABLE(${PROJECT_NAME} ${SOURCE_LIST} "${PROJECT_SOURCE_DIR}/etc/sidewinderd.conf" "${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service")
TARGET_LINK_LIBRARIES(${PROJECT_NAME} stdc++ config++ udev pthread tinyxml2)
INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin)
INSTALL(FILES "${PROJECT_SOURCE_DIR}/etc/sidewinderd.conf" DESTINATION /etc COMPONENT config)
INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service" DESTINATION lib/systemd/system)
================================================
FILE: src/core/device.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef DEVICE_CLASS_H
#define DEVICE_CLASS_H
#include <string>
struct Device {
std::string vendor;
std::string product;
std::string name;
enum class Driver {
LogitechG103,
LogitechG105,
LogitechG710,
SideWinder
} driver;
};
#endif
================================================
FILE: src/core/device_manager.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <cstring>
#include <iostream>
#include <core/device_manager.hpp>
#include <vendor/logitech/g103.hpp>
#include <vendor/logitech/g105.hpp>
#include <vendor/logitech/g710.hpp>
#include <vendor/microsoft/sidewinder.hpp>
constexpr auto VENDOR_MICROSOFT = "045e";
constexpr auto VENDOR_LOGITECH = "046d";
constexpr auto TIMEOUT = 5000;
constexpr auto NUM_POLL = 1;
void DeviceManager::discover() {
for (auto it : devices_) {
struct Device device = it;
struct sidewinderd::DevNode devNode;
if (probe(&device, &devNode) > 0) {
// TODO use a unique identifier
auto it = connected_.find(device.product);
// skip this loop, if device is already connected
if (it != connected_.end()) {
continue;
}
switch (device.driver) {
case Device::Driver::LogitechG103: {
auto keyboard = new LogitechG103(&device, &devNode, config_, process_);
keyboard->connect();
connected_[device.product] = std::unique_ptr<Keyboard>(keyboard);
break;
}
case Device::Driver::LogitechG105: {
auto keyboard = new LogitechG105(&device, &devNode, config_, process_);
keyboard->connect();
connected_[device.product] = std::unique_ptr<Keyboard>(keyboard);
break;
}
case Device::Driver::LogitechG710: {
auto keyboard = new LogitechG710(&device, &devNode, config_, process_);
keyboard->connect();
connected_[device.product] = std::unique_ptr<Keyboard>(keyboard);
break;
}
case Device::Driver::SideWinder: {
auto keyboard = new SideWinder(&device, &devNode, config_, process_);
keyboard->connect();
connected_[device.product] = std::unique_ptr<Keyboard>(keyboard);
break;
}
}
}
}
}
int DeviceManager::monitor() {
// create udev object
udev_ = udev_new();
if (!udev_) {
std::cerr << "Can't create udev." << std::endl;
return -1;
}
// set up monitor for /dev/hidraw and /dev/input/event*
monitor_ = udev_monitor_new_from_netlink(udev_, "udev");
udev_monitor_filter_add_match_subsystem_devtype(monitor_, "hidraw", NULL);
udev_monitor_filter_add_match_subsystem_devtype(monitor_, "input", NULL);
udev_monitor_enable_receiving(monitor_);
// get file descriptor for the monitor, so we can use poll() on it
fd_ = udev_monitor_get_fd(monitor_);
// setup poll
pfd_.fd = fd_;
pfd_.events = POLLIN;
// initial discovery of new devices
discover();
struct udev_device *dev;
// run monitoring loop, until we receive a signal
while (process_->isActive()) {
poll(&pfd_, NUM_POLL, TIMEOUT);
dev = udev_monitor_receive_device(monitor_);
if (dev) {
// filter out nullptr returns, else std::string() fails
auto ret = udev_device_get_action(dev);
if (ret) {
std::string action(ret);
if (action == "add") {
discover();
} else if (action == "remove") {
// check for disconnected devices
auto product = udev_device_get_property_value(dev, "ID_MODEL_ID");
if (product) {
unbind(product);
}
}
}
udev_device_unref(dev);
}
}
udev_monitor_unref(monitor_);
udev_unref(udev_);
return 0;
}
int DeviceManager::probe(struct Device *device, struct sidewinderd::DevNode *devNode) {
struct udev_device *dev;
struct udev_enumerate *enumerate;
struct udev_list_entry *devices, *entry;
bool isFound = false;
// create a list of devices in hidraw and input subsystems
enumerate = udev_enumerate_new(udev_);
udev_enumerate_add_match_subsystem(enumerate, "hidraw");
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(entry, devices) {
const char *sysPath, *devNodePath;
sysPath = udev_list_entry_get_name(entry);
dev = udev_device_new_from_syspath(udev_, sysPath);
auto hidraw = udev_device_get_subsystem(dev);
// evaluation from left to right; used to filter out nullptr
if (hidraw && std::string(hidraw) == std::string("hidraw")) {
devNodePath = udev_device_get_devnode(dev);
dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface");
if (!dev) {
std::cerr << "Unable to find parent device." << std::endl;
}
auto bInterfaceNumber = udev_device_get_sysattr_value(dev, "bInterfaceNumber");
if (bInterfaceNumber && std::string(bInterfaceNumber) == std::string("01")) {
dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
auto idVendor = udev_device_get_sysattr_value(dev, "idVendor");
auto idProduct = udev_device_get_sysattr_value(dev, "idProduct");
if (idVendor && std::string(idVendor) == device->vendor
&& idProduct && std::string(idProduct) == device->product) {
devNode->hidraw = devNodePath;
}
}
}
auto input = udev_device_get_subsystem(dev);
auto product = udev_device_get_property_value(dev, "ID_MODEL_ID");
auto vendor = udev_device_get_property_value(dev, "ID_VENDOR_ID");
auto interface = udev_device_get_property_value(dev, "ID_USB_INTERFACE_NUM");
/* find correct /dev/input/event* file */
if (input && std::string(input) == std::string("input")
&& product && std::string(product) == device->product
&& vendor && std::string(vendor) == device->vendor
&& interface && std::string(interface) == "00"
&& udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD")
&& strstr(sysPath, "event")
&& udev_device_get_parent_with_subsystem_devtype(dev, "usb", NULL)) {
devNode->inputEvent = udev_device_get_devnode(dev);
std::clog << "Found device: " << device->vendor << ":" << device->product << std::endl;
isFound = true;
}
udev_device_unref(dev);
}
/* free the enumerator object */
udev_enumerate_unref(enumerate);
return isFound;
}
void DeviceManager::unbind(std::string product) {
auto it = connected_.find(product);
if (it != connected_.end() && it->second->isConnected()) {
connected_.erase(product);
}
}
DeviceManager::DeviceManager(libconfig::Config *config, Process *process) {
// list of supported devices
devices_ = {
{VENDOR_MICROSOFT, "074b", "Microsoft SideWinder X6",
Device::Driver::SideWinder},
{VENDOR_MICROSOFT, "0768", "Microsoft SideWinder X4",
Device::Driver::SideWinder},
{VENDOR_LOGITECH, "c248", "Logitech G105",
Device::Driver::LogitechG105},
{VENDOR_LOGITECH, "c24b", "Logitech G103",
Device::Driver::LogitechG103},
{VENDOR_LOGITECH, "c24d", "Logitech G710+",
Device::Driver::LogitechG710}
};
config_ = config;
process_ = process;
udev_ = nullptr;
monitor_ = nullptr;
}
DeviceManager::~DeviceManager() {
// remove all connected devices
connected_.clear();
if (udev_) {
udev_unref(udev_);
}
}
================================================
FILE: src/core/device_manager.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef DEVICE_MANAGER_CLASS_H
#define DEVICE_MANAGER_CLASS_H
#include <map>
#include <string>
#include <vector>
#include <libudev.h>
#include <poll.h>
#include <libconfig.h++>
#include <device_data.hpp>
#include <process.hpp>
#include <core/device.hpp>
#include <core/keyboard.hpp>
class DeviceManager {
public:
int monitor();
DeviceManager(libconfig::Config *config, Process *process);
~DeviceManager();
private:
int fd_;
std::map<std::string, std::unique_ptr<Keyboard>> connected_;
std::vector<Device> devices_;
struct pollfd pfd_;
struct udev *udev_;
struct udev_monitor *monitor_;
libconfig::Config *config_;
Process *process_;
void discover();
int probe(struct Device *device, struct sidewinderd::DevNode *devNode);
void unbind(std::string product);
};
#endif
================================================
FILE: src/core/hid_interface.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <iostream>
#include <linux/hidraw.h>
#include <sys/ioctl.h>
#include <core/hid_interface.hpp>
unsigned char HidInterface::getReport(unsigned char report) {
unsigned char buf[2] {};
buf[0] = report;
int ret = ioctl(*fd_, HIDIOCGFEATURE(sizeof(buf)), buf);
if (ret < 0) {
std::cerr << "Error getting HID feature report." << std::endl;
} else {
std::cout << "getFeatureReport(" << static_cast<int>(report)
<< ") returned: " << std::hex
<< static_cast<int>(buf[0]) << " "
<< static_cast<int>(buf[1]) << std::endl;
}
return buf[1];
}
void HidInterface::setReport(unsigned char report, unsigned char value) {
unsigned char buf[2];
/* buf[0] is Report ID, buf[1] is value */
buf[0] = report;
buf[1] = value;
/* TODO: check return value */
int ret = ioctl(*fd_, HIDIOCSFEATURE(sizeof(buf)), buf);
if (ret < 0) {
std::cerr << "Error setting HID feature report." << std::endl;
}
}
HidInterface::HidInterface(int *fd) {
fd_ = fd;
}
================================================
FILE: src/core/hid_interface.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef HID_INTERFACE_CLASS_H
#define HID_INTERFACE_CLASS_H
#include <string>
class HidInterface {
public:
unsigned char getReport(unsigned char report);
void setReport(unsigned char report, unsigned char value);
HidInterface(int *fd);
private:
int *fd_;
};
#endif
================================================
FILE: src/core/key.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <iostream>
#include <sstream>
#include "key.hpp"
/**
* Assembles relative path to Macro file.
*/
std::string Key::getMacroPath(int profile) {
std::stringstream macroPath;
macroPath << "profile_" << profile + 1 << "/" << "s" << keyData_->index << ".xml";
return macroPath.str();
}
/**
* Constructor initializing structs.
*/
Key::Key(struct KeyData *keyData) {
keyData_ = keyData;
}
================================================
FILE: src/core/key.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef KEY_CLASS_H
#define KEY_CLASS_H
#include <string>
/**
* Struct for storing and passing key data.
*
* @var index key index
*/
struct KeyData {
int index;
/**
* Enum class to classify key type.
*
* @var Unknown not classified type
* @var Macro key type for macro keys, e.g. S1 on Microsoft Sidewinder X4
* @var Extra key type for extra keys, e.g. Bank Switch key on Microsoft
* Sidewinder X4
*/
enum class KeyType {
Unknown,
Macro,
Extra
} type;
};
/**
* Class representing a key.
*
* Currently, it only works for Macro keys.
*/
class Key {
public:
std::string getMacroPath(int profile);
Key(struct KeyData *keyData);
private:
struct KeyData *keyData_;
};
#endif
================================================
FILE: src/core/keyboard.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <cstdio>
#include <ctime>
#include <iostream>
#include <sstream>
#include <thread>
#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "keyboard.hpp"
constexpr auto TIMEOUT = 5000;
bool Keyboard::isConnected() {
return isConnected_;
}
void Keyboard::connect() {
isConnected_ = true;
listenThread_ = std::thread(&Keyboard::listen, this);
}
void Keyboard::disconnect() {
isConnected_ = false;
}
void Keyboard::setupPoll() {
fds[0].fd = fd_;
fds[0].events = POLLIN;
/* ignore second fd for now */
fds[1].fd = -1;
fds[1].events = POLLIN;
}
/* TODO: interrupt and exit play_macro when any macro_key has been pressed */
void Keyboard::playMacro(std::string macroPath, VirtualInput *virtInput) {
tinyxml2::XMLDocument xmlDoc;
xmlDoc.LoadFile(macroPath.c_str());
if(!xmlDoc.ErrorID()) {
tinyxml2::XMLElement* root = xmlDoc.FirstChildElement("Macro");
for (tinyxml2::XMLElement* child = root->FirstChildElement(); child; child = child->NextSiblingElement()) {
if (child->Name() == std::string("KeyBoardEvent")) {
bool isPressed;
int key = std::atoi(child->GetText());
child->QueryBoolAttribute("Down", &isPressed);
virtInput->sendEvent(EV_KEY, key, isPressed);
} else if (child->Name() == std::string("DelayEvent")) {
int delay = std::atoi(child->GetText());
struct timespec request, remain;
/*
* value is given in milliseconds, so we need to split it into
* seconds and nanoseconds. nanosleep() is interruptable and saves
* the remaining sleep time.
*/
request.tv_sec = delay / 1000;
delay = delay - (request.tv_sec * 1000);
request.tv_nsec = 1000000L * delay;
nanosleep(&request, &remain);
}
}
}
}
/*
* Macro recording captures delays by default. Use the configuration to disable
* capturing delays.
*/
void Keyboard::recordMacro(std::string path, Led *ledRecord, const int keyRecord) {
struct timeval prev;
struct KeyData keyData;
prev.tv_usec = 0;
prev.tv_sec = 0;
std::cout << "Start Macro Recording on " << devNode_.inputEvent << std::endl;
process_->privilege();
evfd_ = open(devNode_.inputEvent.c_str(), O_RDONLY | O_NONBLOCK);
process_->unprivilege();
if (evfd_ < 0) {
std::cout << "Can't open input event file" << std::endl;
}
/* additionally monitor /dev/input/event* with poll */
fds[1].fd = evfd_;
tinyxml2::XMLDocument doc;
tinyxml2::XMLNode* root = doc.NewElement("Macro");
/* start root element "Macro" */
doc.InsertFirstChild(root);
bool isRecordMode = true;
while (isRecordMode) {
keyData = pollDevice(2);
if (keyData.index == keyRecord && keyData.type == KeyData::KeyType::Extra) {
ledRecord->off();
isRecordMode = false;
}
struct input_event inev;
read(evfd_, &inev, sizeof(struct input_event));
if (inev.type == EV_KEY && inev.value != 2) {
/* only capturing delays, if capture_delays is set to true */
if (prev.tv_usec && config_->lookup("capture_delays")) {
auto diff = (inev.time.tv_usec + 1000000 * inev.time.tv_sec) - (prev.tv_usec + 1000000 * prev.tv_sec);
auto delay = diff / 1000;
/* start element "DelayEvent" */
tinyxml2::XMLElement* DelayEvent = doc.NewElement("DelayEvent");
DelayEvent->SetText(static_cast<int>(delay));
root->InsertEndChild(DelayEvent);
}
/* start element "KeyBoardEvent" */
tinyxml2::XMLElement* KeyBoardEvent = doc.NewElement("KeyBoardEvent");
if (inev.value) {
KeyBoardEvent->SetAttribute("Down", true);
} else {
KeyBoardEvent->SetAttribute("Down", false);
}
KeyBoardEvent->SetText(inev.code);
root->InsertEndChild(KeyBoardEvent);
prev = inev.time;
}
}
/* write XML document */
if (doc.SaveFile(path.c_str())) {
std::cout << "Error XML SaveFile" << std::endl;
}
std::cout << "Exit Macro Recording" << std::endl;
/* remove event file from poll fds */
fds[1].fd = -1;
close(evfd_);
}
struct KeyData Keyboard::pollDevice(nfds_t nfds) {
/*
* poll() checks the device for any activities and blocks the loop,
* either until an event has occured, or the timeout has been reached.
* This leads to a very efficient polling mechanism.
*/
poll(fds, nfds, TIMEOUT);
// check, if device has been disconnected
if (fds->revents & POLLHUP || fds->revents & POLLERR) {
disconnect();
return KeyData();
}
struct KeyData keyData = getInput();
return keyData;
}
void Keyboard::listen() {
while (process_->isActive() && isConnected()) {
struct KeyData keyData = pollDevice(1);
handleKey(&keyData);
}
}
void Keyboard::handleRecordMode(Led *ledRecord, const int keyRecord) {
bool isRecordMode = true;
/* record LED solid light */
ledRecord->on();
while (isRecordMode) {
struct KeyData keyData = pollDevice(1);
if (keyData.type == KeyData::KeyType::Unknown
|| !keyData.index) {
/* skip iteration if event is unknown or index is 0 */
continue;
} else if (keyData.type == KeyData::KeyType::Macro) {
/* record LED should blink */
ledRecord->blink();
isRecordMode = false;
Key key(&keyData);
recordMacro(key.getMacroPath(profile_), ledRecord, keyRecord);
} else if (keyData.type == KeyData::KeyType::Extra) {
/* deactivate Record LED */
ledRecord->off();
isRecordMode = false;
if (keyData.index != keyRecord) {
handleKey(&keyData);
}
}
}
}
Keyboard::Keyboard(struct Device *device,
sidewinderd::DevNode *devNode, libconfig::Config *config,
Process *process) : hid_{&fd_} {
config_ = config;
process_ = process;
device_ = *device;
devNode_ = *devNode;
virtInput_ = new VirtualInput(&device_, &devNode_, process_);
profile_ = 0;
isConnected_ = true;
for (int i = MIN_PROFILE; i < MAX_PROFILE; i++) {
std::stringstream profileFolderPath;
profileFolderPath << "profile_" << i + 1;
mkdir(profileFolderPath.str().c_str(), S_IRWXU);
}
/* open file descriptor with root privileges */
process_->privilege();
fd_ = open(devNode->hidraw.c_str(), O_RDWR | O_NONBLOCK);
process_->unprivilege();
/* TODO: destruct, if interface can't be accessed */
if (fd_ < 0) {
std::cout << "Can't open hidraw interface" << std::endl;
}
setupPoll();
std::cerr << "Keyboard Constructor" << std::endl;
}
Keyboard::~Keyboard() {
std::cerr << "Keyboard Destructor" << std::endl;
delete virtInput_;
close(fd_);
}
================================================
FILE: src/core/keyboard.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef KEYBOARD_CLASS_H
#define KEYBOARD_CLASS_H
#include <string>
#include <thread>
#include <poll.h>
#include <libconfig.h++>
#include <process.hpp>
#include <device_data.hpp>
#include <core/device.hpp>
#include <core/hid_interface.hpp>
#include <core/key.hpp>
#include <core/led.hpp>
#include <core/virtual_input.hpp>
/* constants */
const int MAX_BUF = 8;
const int MIN_PROFILE = 0;
const int MAX_PROFILE = 3;
class Keyboard {
public:
bool isConnected();
void connect();
void disconnect();
void listen();
Keyboard(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
virtual ~Keyboard();
protected:
bool isConnected_;
int profile_;
int fd_, evfd_;
std::thread listenThread_;
Process *process_;
struct pollfd fds[2];
struct Device device_;
libconfig::Config *config_;
sidewinderd::DevNode devNode_;
HidInterface hid_;
VirtualInput *virtInput_;
virtual struct KeyData getInput() = 0;
void setupPoll();
static void playMacro(std::string macroPath, VirtualInput *virtInput);
void recordMacro(std::string path, Led *ledRecord, const int keyRecord);
struct KeyData pollDevice(nfds_t nfds);
virtual void handleKey(struct KeyData *keyData) = 0;
void handleRecordMode(Led *ledRecord, const int keyRecord);
};
#endif
================================================
FILE: src/core/led.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <iostream>
#include <core/led.hpp>
void Led::on() {
auto report = hid_->getReport(report_);
auto buf = report;
if (type_ == LedType::Profile) {
// clear out all LEDs, but Indicator LEDs
buf &= group_->getIndicatorMask();
}
buf |= led_;
// don't call, if there are no changes
if (buf != report) {
hid_->setReport(report_, buf);
}
}
void Led::off() {
auto buf = hid_->getReport(report_);
buf &= ~led_;
hid_->setReport(report_, buf);
}
void Led::blink() {
if (blink_) {
auto buf = hid_->getReport(report_);
buf &= ~led_;
buf |= blink_;
hid_->setReport(report_, buf);
} else {
/*
* TODO Implement non-blocking software emulated blink, using
* std::thread and std::chrono. It should start blinking, when
* Led::blink() has been called and stop, when Led::off() has
* been called. 1 second on, 1 second off sounds like a good
* rhythm. It would be perfect, if one could interrupt the blink
* anytime, even in a this_thread::wait_for().
* Until it's implemented, let's use a solid light.
*/
on();
}
}
void Led::registerBlink(unsigned char led) {
blink_ = led;
}
void Led::setLedType(LedType type) {
type_ = type;
if (type_ == LedType::Indicator) {
auto indicator= group_->getIndicatorMask();
indicator |= led_;
group_->setIndicatorMask(indicator);
}
}
Led::Led(unsigned char report, unsigned char led, LedGroup *group) {
report_ = report;
led_ = led;
group_ = group;
blink_ = 0;
type_ = LedType::Common;
hid_ = group_->getHidInterface();
// initial LED state is off
off();
}
================================================
FILE: src/core/led.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef LED_CLASS_H
#define LED_CLASS_H
#include <core/hid_interface.hpp>
#include <core/led_group.hpp>
enum class LedType {
Common, // common LEDs don't have special handling
Profile, // profile LEDs are exclusively lit
Indicator // indicator LEDs are not allowed to be overwritten
};
class Led {
public:
/**
* Turns LED on.
*/
void on();
/**
* Turns LED off.
*/
void off();
/**
* Sets LED to blinking mode. Falls back to software emulation,
* if there is no hardware support.
*/
void blink();
/**
* If LED supports blinking via hardware, set report ID and
* value using this function.
* @param led blink specific HID report value
*/
void registerBlink(unsigned char led);
/**
* Sets LedType.
* @param type LedType can be Common, Profile or Indicator.
*/
void setLedType(LedType type);
Led(unsigned char report, unsigned char led, LedGroup *group);
private:
unsigned char report_;
unsigned char led_;
unsigned char blink_;
LedGroup *group_;
LedType type_;
HidInterface *hid_;
};
#endif
================================================
FILE: src/core/led_group.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <core/led_group.hpp>
unsigned char LedGroup::getIndicatorMask() {
return indicator_;
}
void LedGroup::setIndicatorMask(unsigned char indicator) {
indicator_ = indicator;
}
HidInterface *LedGroup::getHidInterface() {
return hid_;
}
LedGroup::LedGroup(HidInterface *hid) {
hid_ = hid;
indicator_ = 0;
}
================================================
FILE: src/core/led_group.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef LED_GROUP_CLASS_H
#define LED_GROUP_CLASS_H
#include <core/hid_interface.hpp>
class LedGroup {
public:
unsigned char getIndicatorMask();
void setIndicatorMask(unsigned char indicator);
HidInterface *getHidInterface();
LedGroup(HidInterface *hid);
private:
unsigned char indicator_;
HidInterface *hid_;
};
#endif
================================================
FILE: src/core/virtual_input.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <cstdio>
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <linux/uinput.h>
#include <sys/ioctl.h>
#include "virtual_input.hpp"
/**
* Method for sending input events to the operating system.
*
* @param type type of input event, e.g. EV_KEY
* @param code keycode defined in header file input.h
* @param value value the event carries, e.g. EV_KEY: 0 represents release, 1
* keypress and 2 autorepeat
*/
void VirtualInput::sendEvent(short type, short code, int value) {
struct input_event inev = input_event();
inev.type = type;
inev.code = code;
inev.value = value;
write(uifd_, &inev, sizeof(struct input_event));
inev.type = EV_SYN;
inev.code = 0;
inev.value = 0;
write(uifd_, &inev, sizeof(struct input_event));
}
/**
* Constructor setting up operating system specific back-ends.
*/
VirtualInput::VirtualInput(struct Device *device, sidewinderd::DevNode *devNode, Process *process) {
process_ = process;
device_ = device;
devNode_ = devNode;
/* for Linux */
createUidev();
}
VirtualInput::~VirtualInput() {
close(uifd_);
}
/**
* Creating a uinput virtual input device under Linux.
*/
void VirtualInput::createUidev() {
/* open uinput device with root privileges */
process_->privilege();
uifd_ = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (uifd_ < 0) {
uifd_ = open("/dev/input/uinput", O_WRONLY | O_NONBLOCK);
if (uifd_ < 0) {
std::cout << "Can't open uinput" << std::endl;
}
}
process_->unprivilege();
/* set all keybits */
ioctl(uifd_, UI_SET_EVBIT, EV_KEY);
for (int i = KEY_ESC; i <= KEY_KPDOT; i++) {
ioctl(uifd_, UI_SET_KEYBIT, i);
}
for (int i = KEY_ZENKAKUHANKAKU; i <= KEY_F24; i++) {
ioctl(uifd_, UI_SET_KEYBIT, i);
}
for (int i = KEY_PLAYCD; i <= KEY_MICMUTE; i++) {
ioctl(uifd_, UI_SET_KEYBIT, i);
}
/* uinput device details */
struct uinput_user_dev uidev = uinput_user_dev();
/* TODO: copy device's name */
snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "Sidewinderd");
uidev.id.bustype = BUS_USB;
uidev.id.vendor = std::stoi(device_->vendor, nullptr, 16);
uidev.id.product = std::stoi(device_->product, nullptr, 16);
uidev.id.version = 1;
/* write uinput device details */
write(uifd_, &uidev, sizeof(struct uinput_user_dev));
/* create uinput device */
ioctl(uifd_, UI_DEV_CREATE);
}
================================================
FILE: src/core/virtual_input.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef VIRTUALINPUT_CLASS_H
#define VIRTUALINPUT_CLASS_H
#include <process.hpp>
#include <device_data.hpp>
#include <core/device.hpp>
/**
* Class representing a virtual input device.
*
* Needed to send key events to the operating system. For Linux, uinput is used
* as the back-end.
*/
class VirtualInput {
public:
void sendEvent(short type, short code, int value);
VirtualInput(struct Device *device, sidewinderd::DevNode *devNode, Process *process);
~VirtualInput();
private:
int uifd_; /**< uinput device file descriptor */
Process *process_; /**< process object for setting privileges */
Device *device_; /**< device information */
sidewinderd::DevNode *devNode_; /**< device information */
void createUidev();
};
#endif
================================================
FILE: src/device_data.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef DEVICEDATA_CLASS_H
#define DEVICEDATA_CLASS_H
#include <string>
#include <vector>
namespace sidewinderd {
/**
* Struct for storing and passing paths to relevant /dev/<node> files.
*/
struct DevNode {
std::string hidraw, inputEvent; /**< path to hidraw and input event */
};
};
#endif
================================================
FILE: src/main.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <iostream>
#include <string>
#include <getopt.h>
#include <libconfig.h++>
#include <process.hpp>
#include <core/device_manager.hpp>
void help(std::string name) {
std::cerr << "Usage: " << name << " [options]" << std::endl
<< std::endl
<< "Options:" << std::endl
<< " -c, --config=<file> Override default configuration file path" << std::endl
<< " -d, --daemon Run process as daemon" << std::endl
<< " -h, --help Print this screen" << std::endl
<< " -v, --version Print program version" << std::endl;
}
void setupConfig(libconfig::Config *config, std::string configFilePath = "/etc/sidewinderd.conf") {
try {
config->readFile(configFilePath.c_str());
} catch (const libconfig::FileIOException &fioex) {
std::cerr << "I/O error while reading file." << std::endl;
} catch (const libconfig::ParseException &pex) {
std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine() << " - " << pex.getError() << std::endl;
}
libconfig::Setting &root = config->getRoot();
if (!root.exists("user")) {
root.add("user", libconfig::Setting::TypeString) = "root";
}
if (!root.exists("capture_delays")) {
root.add("capture_delays", libconfig::Setting::TypeBoolean) = true;
}
if (!root.exists("pid-file")) {
root.add("pid-file", libconfig::Setting::TypeString) = "/var/run/sidewinderd.pid";
}
if (!root.exists("encrypted_workdir")) {
root.add("encrypted_workdir", libconfig::Setting::TypeBoolean) = false;
}
}
int main(int argc, char *argv[]) {
/* object for managing runtime information */
Process process;
/* set program name */
process.setName(argv[0]);
/* handling command-line options */
static struct option longOptions[] = {
{"config", required_argument, 0, 'c'},
{"daemon", no_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
int opt, index = 0;
std::string configFilePath;
/* flags */
bool shouldDaemonize = false;
while ((opt = getopt_long(argc, argv, ":c:dhv", longOptions, &index)) != -1) {
switch (opt) {
case 'c':
configFilePath = optarg;
break;
case 'd':
shouldDaemonize = true;
break;
case 'h':
help(process.getName());
return EXIT_SUCCESS;
case 'v':
std::cerr << process.getName() << " " << process.getVersion() << std::endl;
return EXIT_SUCCESS;
case ':':
std::cerr << "Missing argument." << std::endl;
return EXIT_FAILURE;
case '?':
std::cerr << "Unknown option." << std::endl;
return EXIT_FAILURE;
default:
std::cerr << "Unexpected error." << std::endl;
return EXIT_FAILURE;
}
}
/* reading config file */
libconfig::Config config;
if (configFilePath.empty()) {
setupConfig(&config);
} else {
setupConfig(&config, configFilePath);
}
/* daemonize, if flag has been set */
if (shouldDaemonize) {
int ret = process.daemonize();
if (ret > 0) {
return EXIT_SUCCESS;
} else if (ret < 0) {
return EXIT_FAILURE;
}
}
/* creating pid file for single instance mechanism */
if (process.createPid(config.lookup("pid-file"))) {
return EXIT_FAILURE;
}
/* setting gid and uid to configured user */
if (process.applyUser(config.lookup("user"))) {
return EXIT_FAILURE;
}
// setting up working directory
std::string workdir;
if (config.exists("workdir")) {
workdir = config.lookup("workdir").c_str();
}
if (process.createWorkdir(workdir, config.lookup("encrypted_workdir"))) {
return EXIT_FAILURE;
}
std::clog << "Started sidewinderd." << std::endl;
process.setActive(true);
DeviceManager deviceManager(&config, &process);
deviceManager.monitor();
process.destroyPid();
std::clog << "Stopped sidewinderd." << std::endl;
return EXIT_SUCCESS;
}
================================================
FILE: src/process.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <chrono>
#include <csignal>
#include <iostream>
#include <thread>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "process.hpp"
/* constants */
constexpr auto version = "0.4.4";
constexpr auto wait = 1;
std::atomic<bool> Process::isActive_;
bool Process::isActive() {
return isActive_;
}
void Process::setActive(bool isActive) {
isActive_ = isActive;
}
std::string Process::getName() {
if (name_.empty()) {
name_ = "sidewinderd";
}
return name_;
}
void Process::setName(std::string name) {
name_ = name;
}
int Process::daemonize() {
pid_t pid, sid;
pid = fork();
if (pid < 0) {
std::cerr << "Error creating daemon." << std::endl;
return -1;
}
if (pid > 0) {
return 1;
}
sid = setsid();
if (sid < 0) {
std::cerr << "Error setting sid." << std::endl;
return -1;
}
pid = fork();
if (pid < 0) {
std::cerr << "Error forking second time." << std::endl;
return -1;
}
if (pid > 0) {
return 1;
}
umask(0);
chdir("/");
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
return 0;
}
int Process::createPid(std::string pidPath) {
pidPath_ = pidPath;
pidFd_ = open(pidPath_.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (pidFd_ < 0) {
std::cerr << "PID file could not be created." << std::endl;
return -1;
}
if (flock(pidFd_, LOCK_EX | LOCK_NB) < 0) {
std::cerr << "Could not lock PID file, another instance is already running." << std::endl;
close(pidFd_);
return -1;
}
hasPid_ = true;
return 0;
}
void Process::destroyPid() {
if (pidFd_) {
flock(pidFd_, LOCK_UN);
close(pidFd_);
}
if (hasPid_) {
unlink(pidPath_.c_str());
hasPid_ = false;
}
}
int Process::applyUser(std::string user) {
user_ = user;
pw_ = getpwnam(user_.c_str());
if (pw_) {
setegid(pw_->pw_gid);
seteuid(pw_->pw_uid);
} else {
std::cerr << "User not found." << std::endl;
return -1;
}
return 0;
}
int Process::createWorkdir(std::string directory, bool isEncrypted) {
if (user_.empty()) {
return 1;
}
std::string workdir;
if (directory.empty()) {
std::string xdgData;
workdir = pw_->pw_dir;
// find user-specific data directory
if (const char *env = std::getenv("XDG_DATA_HOME")) {
xdgData = env;
if (!xdgData.empty()) {
workdir = xdgData;
}
} else {
// fallback to default
xdgData = "/.local/share";
workdir.append(xdgData);
}
} else {
workdir = directory;
}
// wait until encrypted drive becomes available
if (isEncrypted) {
while (access(workdir.c_str(), F_OK)) {
std::this_thread::sleep_for(std::chrono::seconds(wait));
}
}
// creating sidewinderd directory
if (directory.empty()) {
workdir.append("/sidewinderd");
mkdir(workdir.c_str(), S_IRWXU);
}
if (chdir(workdir.c_str())) {
std::cerr << "Error accessing " << workdir << "." << std::endl;
return -1;
}
return 0;
}
void Process::privilege() {
seteuid(0);
}
void Process::unprivilege() {
if (user_.empty()) {
return;
}
seteuid(pw_->pw_uid);
}
std::string Process::getVersion() {
return version;
}
void Process::sigHandler(int sig) {
std::cerr << std::endl << "Stop signal received." << std::endl;
switch(sig) {
case SIGINT:
setActive(false);
break;
case SIGTERM:
setActive(false);
break;
}
}
Process::Process() {
isActive_ = false;
hasPid_ = false;
pidFd_ = 0;
/* signal handling */
struct sigaction action;
action.sa_handler = sigHandler;
sigaction(SIGINT, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
}
Process::~Process() {
destroyPid();
}
================================================
FILE: src/process.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef PROCESS_CLASS_H
#define PROCESS_CLASS_H
#include <atomic>
#include <string>
#include <pwd.h>
class Process {
public:
static bool isActive();
static void setActive(bool isActive);
std::string getName();
void setName(std::string name);
int daemonize();
int createPid(std::string pidPath);
void destroyPid();
int applyUser(std::string user);
int createWorkdir(std::string directory, bool isEncrypted);
void privilege();
void unprivilege();
std::string getVersion();
Process();
~Process();
private:
static std::atomic<bool> isActive_;
bool hasPid_;
int pidFd_;
std::string name_;
std::string user_;
std::string pidPath_;
struct passwd *pw_;
static void sigHandler(int sig);
};
#endif
================================================
FILE: src/vendor/logitech/g103.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <cstdio>
#include <ctime>
#include <iostream>
#include <thread>
#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "g103.hpp"
/* constants */
constexpr auto G103_FEATURE_REPORT_MACRO = 0x08;
constexpr auto G103_FEATURE_REPORT_MACRO_SIZE = 7;
void LogitechG103::setProfile(int profile) {
profile_ = profile;
}
/*
* get_input() checks, which keys were pressed. The macro keys are packed in a
* 3-byte buffer.
*/
/*
* TODO: only return latest pressed key, if multiple keys have been pressed at
* the same time.
*/
struct KeyData LogitechG103::getInput() {
struct KeyData keyData = KeyData();
int key, nBytes;
unsigned char buf[MAX_BUF];
nBytes = read(fd_, buf, MAX_BUF);
if (nBytes == 3 && buf[0] == 0x03) {
/*
* cutting off buf[0], which is used to differentiate between macro and
* media keys. Our task is now to translate the buffer codes to
* something we can work with. Here is a table, where you can look up
* the keys and buffer, if you want to improve the current method:
*
* G1 0x03 0x01 0x00 - buf[1]
* G2 0x03 0x02 0x00 - buf[1]
* G3 0x03 0x04 0x00 - buf[1]
* G4 0x03 0x08 0x00 - buf[1]
* G5 0x03 0x10 0x00 - buf[1]
* G6 0x03 0x20 0x00 - buf[1]
*/
if (buf[2] == 0) {
key = (static_cast<int>(buf[1]));
key = ffs(key);
if (key) {
keyData.index = key;
keyData.type = KeyData::KeyType::Macro;
}
}
}
return keyData;
}
void LogitechG103::handleKey(struct KeyData *keyData) {
if (keyData->index != 0) {
if (keyData->type == KeyData::KeyType::Macro) {
Key key(keyData);
std::string macroPath = key.getMacroPath(profile_);
std::thread thread(playMacro, macroPath, virtInput_);
thread.detach();
}
}
}
void LogitechG103::resetMacroKeys() {
/* we need to zero out the report, so macro keys don't emit F-keys */
unsigned char buf[G103_FEATURE_REPORT_MACRO_SIZE] = {};
/* buf[0] is Report ID */
buf[0] = G103_FEATURE_REPORT_MACRO;
ioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);
}
LogitechG103::LogitechG103(struct Device *device,
sidewinderd::DevNode *devNode, libconfig::Config *config,
Process *process) :
Keyboard::Keyboard(device, devNode, config, process) {
resetMacroKeys();
// set profile to default, as no profile switching is supported
setProfile(0);
}
LogitechG103::~LogitechG103() {
std::cerr << "LogitechG103 Destructor" << std::endl;
// keyboard is not connected anymore, join the thread
if (listenThread_.joinable()) {
listenThread_.join();
}
}
================================================
FILE: src/vendor/logitech/g103.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef LOGITECH_G103_CLASS_H
#define LOGITECH_G103_CLASS_H
#include <core/keyboard.hpp>
class LogitechG103 : public Keyboard {
public:
LogitechG103(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
~LogitechG103();
protected:
struct KeyData getInput();
void handleKey(struct KeyData *keyData);
private:
void setProfile(int profile);
void resetMacroKeys();
};
#endif
================================================
FILE: src/vendor/logitech/g105.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <cstdio>
#include <ctime>
#include <iostream>
#include <thread>
#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "g105.hpp"
/* constants */
constexpr auto G105_FEATURE_REPORT_LED = 0x06;
constexpr auto G105_FEATURE_REPORT_MACRO = 0x08;
constexpr auto G105_FEATURE_REPORT_MACRO_SIZE = 7;
constexpr auto G105_LED_M1 = 0x01;
constexpr auto G105_LED_M2 = 0x02;
constexpr auto G105_LED_M3 = 0x04;
constexpr auto G105_LED_MR = 0x08;
constexpr auto G105_KEY_M1 = 0x01;
constexpr auto G105_KEY_M2 = 0x02;
constexpr auto G105_KEY_M3 = 0x03;
constexpr auto G105_KEY_MR = 0x04;
void LogitechG105::setProfile(int profile) {
profile_ = profile;
switch (profile_) {
case 0: ledProfile1_.on(); break;
case 1: ledProfile2_.on(); break;
case 2: ledProfile3_.on(); break;
}
}
/*
* get_input() checks, which keys were pressed. The macro keys are packed in a
* 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.
*/
/*
* TODO: only return latest pressed key, if multiple keys have been pressed at
* the same time.
*/
struct KeyData LogitechG105::getInput() {
struct KeyData keyData = KeyData();
int key, nBytes;
unsigned char buf[MAX_BUF];
nBytes = read(fd_, buf, MAX_BUF);
if (nBytes == 3 && buf[0] == 0x03) {
/*
* cutting off buf[0], which is used to differentiate between macro and
* media keys. Our task is now to translate the buffer codes to
* something we can work with. Here is a table, where you can look up
* the keys and buffer, if you want to improve the current method:
*
* G1 0x03 0x01 0x00 - buf[1]
* G2 0x03 0x02 0x00 - buf[1]
* G3 0x03 0x04 0x00 - buf[1]
* G4 0x03 0x08 0x00 - buf[1]
* G5 0x03 0x10 0x00 - buf[1]
* G6 0x03 0x20 0x00 - buf[1]
* M1 0x03 0x00 0x01 - buf[2]
* M2 0x03 0x00 0x02 - buf[2]
* M3 0x03 0x00 0x04 - buf[2]
* MR 0x03 0x00 0x08 - buf[2]
*/
if (buf[2] == 0) {
key = (static_cast<int>(buf[1]));
key = ffs(key);
if (key) {
keyData.index = key;
keyData.type = KeyData::KeyType::Macro;
}
} else if (buf[1] == 0) {
key = (static_cast<int>(buf[2]));
key = ffs(key);
if (key) {
keyData.index = key;
keyData.type = KeyData::KeyType::Extra;
}
}
}
return keyData;
}
void LogitechG105::handleKey(struct KeyData *keyData) {
if (keyData->index != 0) {
if (keyData->type == KeyData::KeyType::Macro) {
Key key(keyData);
std::string macroPath = key.getMacroPath(profile_);
std::thread thread(playMacro, macroPath, virtInput_);
thread.detach();
} else if (keyData->type == KeyData::KeyType::Extra) {
if (keyData->index == G105_KEY_M1) {
/* M1 key */
setProfile(0);
} else if (keyData->index == G105_KEY_M2) {
/* M2 key */
setProfile(1);
} else if (keyData->index == G105_KEY_M3) {
/* M3 key */
setProfile(2);
} else if (keyData->index == G105_KEY_MR) {
/* MR key */
handleRecordMode(&ledRecord_, G105_KEY_MR);
}
}
}
}
void LogitechG105::resetMacroKeys() {
/* we need to zero out the report, so macro keys don't emit numbers */
unsigned char buf[G105_FEATURE_REPORT_MACRO_SIZE] = {};
/* buf[0] is Report ID */
buf[0] = G105_FEATURE_REPORT_MACRO;
ioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);
}
LogitechG105::LogitechG105(struct Device *device,
sidewinderd::DevNode *devNode, libconfig::Config *config,
Process *process) :
Keyboard::Keyboard(device, devNode, config, process),
group_{&hid_},
ledProfile1_{G105_FEATURE_REPORT_LED, G105_LED_M1, &group_},
ledProfile2_{G105_FEATURE_REPORT_LED, G105_LED_M2, &group_},
ledProfile3_{G105_FEATURE_REPORT_LED, G105_LED_M3, &group_},
ledRecord_{G105_FEATURE_REPORT_LED, G105_LED_MR, &group_} {
ledProfile1_.setLedType(LedType::Profile);
ledProfile2_.setLedType(LedType::Profile);
ledProfile3_.setLedType(LedType::Profile);
ledRecord_.setLedType(LedType::Indicator);
resetMacroKeys();
// set initial LED
ledProfile1_.on();
}
LogitechG105::~LogitechG105() {
std::cerr << "LogitechG105 Destructor" << std::endl;
// keyboard is not connected anymore, join the thread
if (listenThread_.joinable()) {
listenThread_.join();
}
}
================================================
FILE: src/vendor/logitech/g105.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef LOGITECH_G105_CLASS_H
#define LOGITECH_G105_CLASS_H
#include <core/keyboard.hpp>
#include <core/led_group.hpp>
class LogitechG105 : public Keyboard {
public:
LogitechG105(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
~LogitechG105();
protected:
struct KeyData getInput();
void handleKey(struct KeyData *keyData);
private:
LedGroup group_;
Led ledProfile1_;
Led ledProfile2_;
Led ledProfile3_;
Led ledRecord_;
void setProfile(int profile);
void resetMacroKeys();
};
#endif
================================================
FILE: src/vendor/logitech/g710.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <cstdio>
#include <ctime>
#include <iostream>
#include <thread>
#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "g710.hpp"
/* constants */
constexpr auto G710_FEATURE_REPORT_LED = 0x06;
constexpr auto G710_FEATURE_REPORT_MACRO = 0x09;
constexpr auto G710_FEATURE_REPORT_MACRO_SIZE = 13;
constexpr auto G710_LED_M1 = 0x10;
constexpr auto G710_LED_M2 = 0x20;
constexpr auto G710_LED_M3 = 0x40;
constexpr auto G710_LED_MR = 0x80;
constexpr auto G710_KEY_M1 = 0x01;
constexpr auto G710_KEY_M2 = 0x02;
constexpr auto G710_KEY_M3 = 0x03;
constexpr auto G710_KEY_MR = 0x04;
void LogitechG710::setProfile(int profile) {
profile_ = profile;
switch (profile_) {
case 0: ledProfile1_.on(); break;
case 1: ledProfile2_.on(); break;
case 2: ledProfile3_.on(); break;
}
}
/*
* get_input() checks, which keys were pressed. The macro keys are packed in a
* 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.
*/
/*
* TODO: only return latest pressed key, if multiple keys have been pressed at
* the same time.
*/
struct KeyData LogitechG710::getInput() {
struct KeyData keyData = KeyData();
int key, nBytes;
unsigned char buf[MAX_BUF];
nBytes = read(fd_, buf, MAX_BUF);
if (nBytes == 4 && buf[0] == 0x03) {
/*
* cutting off buf[0], which is used to differentiate between macro and
* media keys. Our task is now to translate the buffer codes to
* something we can work with. Here is a table, where you can look up
* the keys and buffer, if you want to improve the current method:
*
* G1 0x03 0x01 0x00 0x00 - buf[1]
* G2 0x03 0x02 0x00 0x00 - buf[1]
* G3 0x03 0x04 0x00 0x00 - buf[1]
* G4 0x03 0x08 0x00 0x00 - buf[1]
* G5 0x03 0x10 0x00 0x00 - buf[1]
* G6 0x03 0x20 0x00 0x00 - buf[1]
* M1 0x03 0x00 0x10 0x00 - buf[2]
* M2 0x03 0x00 0x20 0x00 - buf[2]
* M3 0x03 0x00 0x40 0x00 - buf[2]
* MR 0x03 0x00 0x80 0x00 - buf[2]
*/
if (buf[2] == 0) {
key = (static_cast<int>(buf[1]));
key = ffs(key);
if (key) {
keyData.index = key;
keyData.type = KeyData::KeyType::Macro;
}
} else if (buf[1] == 0) {
key = (static_cast<int>(buf[2])) >> 4;
key = ffs(key);
if (key) {
keyData.index = key;
keyData.type = KeyData::KeyType::Extra;
}
}
}
return keyData;
}
void LogitechG710::handleKey(struct KeyData *keyData) {
if (keyData->index != 0) {
if (keyData->type == KeyData::KeyType::Macro) {
Key key(keyData);
std::string macroPath = key.getMacroPath(profile_);
std::thread thread(playMacro, macroPath, virtInput_);
thread.detach();
} else if (keyData->type == KeyData::KeyType::Extra) {
if (keyData->index == G710_KEY_M1) {
/* M1 key */
setProfile(0);
} else if (keyData->index == G710_KEY_M2) {
/* M2 key */
setProfile(1);
} else if (keyData->index == G710_KEY_M3) {
/* M3 key */
setProfile(2);
} else if (keyData->index == G710_KEY_MR) {
/* MR key */
handleRecordMode(&ledRecord_, G710_KEY_MR);
}
}
}
}
void LogitechG710::resetMacroKeys() {
/* we need to zero out the report, so macro keys don't emit numbers */
unsigned char buf[G710_FEATURE_REPORT_MACRO_SIZE] = {};
/* buf[0] is Report ID */
buf[0] = G710_FEATURE_REPORT_MACRO;
ioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);
}
LogitechG710::LogitechG710(struct Device *device,
sidewinderd::DevNode *devNode, libconfig::Config *config,
Process *process) :
Keyboard::Keyboard(device, devNode, config, process),
group_{&hid_},
ledProfile1_{G710_FEATURE_REPORT_LED, G710_LED_M1, &group_},
ledProfile2_{G710_FEATURE_REPORT_LED, G710_LED_M2, &group_},
ledProfile3_{G710_FEATURE_REPORT_LED, G710_LED_M3, &group_},
ledRecord_{G710_FEATURE_REPORT_LED, G710_LED_MR, &group_} {
ledProfile1_.setLedType(LedType::Profile);
ledProfile2_.setLedType(LedType::Profile);
ledProfile3_.setLedType(LedType::Profile);
ledRecord_.setLedType(LedType::Indicator);
resetMacroKeys();
// set initial LED
ledProfile1_.on();
}
LogitechG710::~LogitechG710() {
std::cerr << "LogitechG710 Destructor" << std::endl;
// keyboard is not connected anymore, join the thread
if (listenThread_.joinable()) {
listenThread_.join();
}
}
================================================
FILE: src/vendor/logitech/g710.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef LOGITECH_G710_PLUS_CLASS_H
#define LOGITECH_G710_PLUS_CLASS_H
#include <core/keyboard.hpp>
#include <core/led_group.hpp>
class LogitechG710 : public Keyboard {
public:
LogitechG710(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
~LogitechG710();
protected:
struct KeyData getInput();
void handleKey(struct KeyData *keyData);
private:
LedGroup group_;
Led ledProfile1_;
Led ledProfile2_;
Led ledProfile3_;
Led ledRecord_;
void setProfile(int profile);
void resetMacroKeys();
};
#endif
================================================
FILE: src/vendor/microsoft/sidewinder.cpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#include <cstdio>
#include <ctime>
#include <iostream>
#include <thread>
#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "sidewinder.hpp"
/* constants */
constexpr auto SW_FEATURE_REPORT = 0x07;
constexpr auto SW_MACRO_PAD = 0x01;
constexpr auto SW_LED_AUTO = 0x02;
constexpr auto SW_LED_P1 = 0x04;
constexpr auto SW_LED_P2 = 0x08;
constexpr auto SW_LED_P3 = 0x10;
constexpr auto SW_LED_RECORD = 0x60;
constexpr auto SW_LED_RECORD_BLINK = 0x40;
constexpr auto SW_KEY_GAMECENTER = 0x10;
constexpr auto SW_KEY_RECORD = 0x11;
constexpr auto SW_KEY_PROFILE = 0x14;
void SideWinder::toggleMacroPad() {
auto report = hid_.getReport(SW_FEATURE_REPORT);
report ^= SW_MACRO_PAD;
macroPad_ = report & SW_MACRO_PAD;
hid_.setReport(SW_FEATURE_REPORT, report);
}
void SideWinder::switchProfile() {
profile_ = (profile_ + 1) % MAX_PROFILE;
switch (profile_) {
case 0: ledProfile1_.on(); break;
case 1: ledProfile2_.on(); break;
case 2: ledProfile3_.on(); break;
}
}
/*
* get_input() checks, which keys were pressed. The macro keys are packed in a
* 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.
*/
/*
* TODO: only return latest pressed key, if multiple keys have been pressed at
* the same time.
*/
struct KeyData SideWinder::getInput() {
struct KeyData keyData = KeyData();
int key, nBytes;
unsigned char buf[MAX_BUF];
nBytes = read(fd_, buf, MAX_BUF);
if (nBytes == 5 && buf[0] == 8) {
/*
* cutting off buf[0], which is used to differentiate between macro and
* media keys. Our task is now to translate the buffer codes to
* something we can work with. Here is a table, where you can look up
* the keys and buffer, if you want to improve the current method:
*
* S1 0x08 0x01 0x00 0x00 0x00 - buf[1]
* S2 0x08 0x02 0x00 0x00 0x00 - buf[1]
* S3 0x08 0x04 0x00 0x00 0x00 - buf[1]
* S4 0x08 0x08 0x00 0x00 0x00 - buf[1]
* S5 0x08 0x10 0x00 0x00 0x00 - buf[1]
* S6 0x08 0x20 0x00 0x00 0x00 - buf[1]
* S7 0x08 0x40 0x00 0x00 0x00 - buf[1]
* S8 0x08 0x80 0x00 0x00 0x00 - buf[1]
* S9 0x08 0x00 0x01 0x00 0x00 - buf[2]
* S10 0x08 0x00 0x02 0x00 0x00 - buf[2]
* S11 0x08 0x00 0x04 0x00 0x00 - buf[2]
* S12 0x08 0x00 0x08 0x00 0x00 - buf[2]
* S13 0x08 0x00 0x10 0x00 0x00 - buf[2]
* S14 0x08 0x00 0x20 0x00 0x00 - buf[2]
* S15 0x08 0x00 0x40 0x00 0x00 - buf[2]
* S16 0x08 0x00 0x80 0x00 0x00 - buf[2]
* S17 0x08 0x00 0x00 0x01 0x00 - buf[3]
* S18 0x08 0x00 0x00 0x02 0x00 - buf[3]
* S19 0x08 0x00 0x00 0x04 0x00 - buf[3]
* S20 0x08 0x00 0x00 0x08 0x00 - buf[3]
* S21 0x08 0x00 0x00 0x10 0x00 - buf[3]
* S22 0x08 0x00 0x00 0x20 0x00 - buf[3]
* S23 0x08 0x00 0x00 0x40 0x00 - buf[3]
* S24 0x08 0x00 0x00 0x80 0x00 - buf[3]
* S25 0x08 0x00 0x00 0x00 0x01 - buf[4]
* S26 0x08 0x00 0x00 0x00 0x02 - buf[4]
* S27 0x08 0x00 0x00 0x00 0x04 - buf[4]
* S28 0x08 0x00 0x00 0x00 0x08 - buf[4]
* S29 0x08 0x00 0x00 0x00 0x10 - buf[4]
* S30 0x08 0x00 0x00 0x00 0x20 - buf[4]
*/
key = (static_cast<int>(buf[1]))
| (static_cast<int>(buf[2]) << 8)
| (static_cast<int>(buf[3]) << 16)
| (static_cast<int>(buf[4]) << 24);
key = ffs(key);
if (key) {
keyData.index = key;
keyData.type = KeyData::KeyType::Macro;
}
} else if (nBytes == 8 && buf[0] == 1 && buf[6]) {
/* buf[0] == 1 means media keys, buf[6] shows pressed key */
keyData.index = buf[6];
keyData.type = KeyData::KeyType::Extra;
}
return keyData;
}
void SideWinder::handleKey(struct KeyData *keyData) {
if (keyData->type == KeyData::KeyType::Macro) {
Key key(keyData);
std::string macroPath = key.getMacroPath(profile_);
std::thread thread(playMacro, macroPath, virtInput_);
thread.detach();
} else if (keyData->type == KeyData::KeyType::Extra) {
if (keyData->index == SW_KEY_GAMECENTER) {
toggleMacroPad();
} else if (keyData->index == SW_KEY_RECORD) {
handleRecordMode(&ledRecord_, SW_KEY_RECORD);
} else if (keyData->index == SW_KEY_PROFILE) {
switchProfile();
}
}
}
SideWinder::SideWinder(struct Device *device,
sidewinderd::DevNode *devNode, libconfig::Config *config,
Process *process) :
Keyboard::Keyboard(device, devNode, config, process),
group_{&hid_},
ledProfile1_{SW_FEATURE_REPORT, SW_LED_P1, &group_},
ledProfile2_{SW_FEATURE_REPORT, SW_LED_P2, &group_},
ledProfile3_{SW_FEATURE_REPORT, SW_LED_P3, &group_},
ledRecord_{SW_FEATURE_REPORT, SW_LED_RECORD, &group_},
ledAuto_{SW_FEATURE_REPORT, SW_LED_AUTO, &group_} {
ledProfile1_.setLedType(LedType::Profile);
ledProfile2_.setLedType(LedType::Profile);
ledProfile3_.setLedType(LedType::Profile);
ledRecord_.setLedType(LedType::Indicator);
ledRecord_.registerBlink(SW_LED_RECORD_BLINK);
ledAuto_.setLedType(LedType::Indicator);
// needed to avoid resetting macropad mode after bank switch
// TODO: use a better solution after Led handling has been rewritten
auto indicator= group_.getIndicatorMask();
indicator |= SW_MACRO_PAD;
group_.setIndicatorMask(indicator);
// set initial LED
ledProfile1_.on();
}
SideWinder::~SideWinder() {
std::cerr << "SideWinder Destructor" << std::endl;
// keyboard is not connected anymore, join the thread
if (listenThread_.joinable()) {
listenThread_.join();
}
}
================================================
FILE: src/vendor/microsoft/sidewinder.hpp
================================================
/**
* Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
*
* This source file is part of Sidewinder daemon and is distributed under the
* MIT License. For more information, see LICENSE file.
*/
#ifndef MICROSOFT_SIDEWINDER_CLASS_H
#define MICROSOFT_SIDEWINDER_CLASS_H
#include <core/keyboard.hpp>
#include <core/led_group.hpp>
class SideWinder : public Keyboard {
public:
SideWinder(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
~SideWinder();
protected:
struct KeyData getInput();
void handleKey(struct KeyData *keyData);
private:
LedGroup group_;
Led ledProfile1_;
Led ledProfile2_;
Led ledProfile3_;
Led ledRecord_;
Led ledAuto_;
unsigned char macroPad_;
void toggleMacroPad();
void switchProfile();
};
#endif
gitextract_np3q0_ka/
├── .gitignore
├── CMakeLists.txt
├── CONTRIBUTING
├── LICENSE
├── README.md
├── cmake/
│ ├── FindConfig++.cmake
│ ├── FindTinyXML2.cmake
│ └── FindUDev.cmake
├── etc/
│ ├── sidewinderd.conf
│ └── sidewinderd.service.in
└── src/
├── CMakeLists.txt
├── core/
│ ├── device.hpp
│ ├── device_manager.cpp
│ ├── device_manager.hpp
│ ├── hid_interface.cpp
│ ├── hid_interface.hpp
│ ├── key.cpp
│ ├── key.hpp
│ ├── keyboard.cpp
│ ├── keyboard.hpp
│ ├── led.cpp
│ ├── led.hpp
│ ├── led_group.cpp
│ ├── led_group.hpp
│ ├── virtual_input.cpp
│ └── virtual_input.hpp
├── device_data.hpp
├── main.cpp
├── process.cpp
├── process.hpp
└── vendor/
├── logitech/
│ ├── g103.cpp
│ ├── g103.hpp
│ ├── g105.cpp
│ ├── g105.hpp
│ ├── g710.cpp
│ └── g710.hpp
└── microsoft/
├── sidewinder.cpp
└── sidewinder.hpp
SYMBOL INDEX (93 symbols across 25 files)
FILE: src/core/device.hpp
type Device (line 13) | struct Device {
type Driver (line 18) | enum class Driver {
FILE: src/core/device_manager.cpp
type Device (line 24) | struct Device
type sidewinderd::DevNode (line 25) | struct sidewinderd::DevNode
type udev_device (line 92) | struct udev_device
type Device (line 128) | struct Device
type sidewinderd::DevNode (line 128) | struct sidewinderd::DevNode
type udev_device (line 129) | struct udev_device
type udev_enumerate (line 130) | struct udev_enumerate
type udev_list_entry (line 131) | struct udev_list_entry
function udev_list_entry_foreach (line 141) | udev_list_entry_foreach(entry, devices) {
FILE: src/core/device_manager.hpp
class DeviceManager (line 25) | class DeviceManager {
type pollfd (line 35) | struct pollfd
type udev (line 36) | struct udev
type udev_monitor (line 37) | struct udev_monitor
type Device (line 41) | struct Device
type sidewinderd::DevNode (line 41) | struct sidewinderd::DevNode
FILE: src/core/hid_interface.hpp
class HidInterface (line 13) | class HidInterface {
FILE: src/core/key.cpp
type KeyData (line 26) | struct KeyData
FILE: src/core/key.hpp
type KeyData (line 18) | struct KeyData {
type KeyType (line 29) | enum class KeyType {
class Key (line 41) | class Key {
type KeyData (line 44) | struct KeyData
type KeyData (line 47) | struct KeyData
FILE: src/core/keyboard.cpp
type timespec (line 65) | struct timespec
type timeval (line 85) | struct timeval
type KeyData (line 86) | struct KeyData
type input_event (line 115) | struct input_event
type input_event (line 116) | struct input_event
type KeyData (line 155) | struct KeyData
type KeyData (line 170) | struct KeyData
type KeyData (line 177) | struct KeyData
type KeyData (line 188) | struct KeyData
type Device (line 212) | struct Device
FILE: src/core/keyboard.hpp
class Keyboard (line 31) | class Keyboard {
type Device (line 37) | struct Device
type pollfd (line 46) | struct pollfd
type Device (line 47) | struct Device
type KeyData (line 52) | struct KeyData
type KeyData (line 56) | struct KeyData
type KeyData (line 57) | struct KeyData
FILE: src/core/led.hpp
type LedType (line 14) | enum class LedType {
class Led (line 20) | class Led {
FILE: src/core/led_group.cpp
function HidInterface (line 18) | HidInterface *LedGroup::getHidInterface() {
FILE: src/core/led_group.hpp
class LedGroup (line 13) | class LedGroup {
FILE: src/core/virtual_input.cpp
type input_event (line 29) | struct input_event
type input_event (line 33) | struct input_event
type input_event (line 37) | struct input_event
type Device (line 43) | struct Device
type uinput_user_dev (line 88) | struct uinput_user_dev
type uinput_user_dev (line 96) | struct uinput_user_dev
FILE: src/core/virtual_input.hpp
class VirtualInput (line 21) | class VirtualInput {
type Device (line 24) | struct Device
FILE: src/device_data.hpp
type sidewinderd (line 14) | namespace sidewinderd {
type DevNode (line 18) | struct DevNode {
FILE: src/main.cpp
function help (line 18) | void help(std::string name) {
function setupConfig (line 28) | void setupConfig(libconfig::Config *config, std::string configFilePath =...
function main (line 56) | int main(int argc, char *argv[]) {
FILE: src/process.cpp
type sigaction (line 222) | struct sigaction
FILE: src/process.hpp
class Process (line 16) | class Process {
type passwd (line 40) | struct passwd
FILE: src/vendor/logitech/g103.cpp
type KeyData (line 41) | struct KeyData
type KeyData (line 42) | struct KeyData
type KeyData (line 75) | struct KeyData
type Device (line 94) | struct Device
FILE: src/vendor/logitech/g103.hpp
class LogitechG103 (line 13) | class LogitechG103 : public Keyboard {
type Device (line 15) | struct Device
type KeyData (line 19) | struct KeyData
type KeyData (line 20) | struct KeyData
FILE: src/vendor/logitech/g105.cpp
type KeyData (line 56) | struct KeyData
type KeyData (line 57) | struct KeyData
type KeyData (line 102) | struct KeyData
type Device (line 135) | struct Device
FILE: src/vendor/logitech/g105.hpp
class LogitechG105 (line 14) | class LogitechG105 : public Keyboard {
type Device (line 16) | struct Device
type KeyData (line 20) | struct KeyData
type KeyData (line 21) | struct KeyData
FILE: src/vendor/logitech/g710.cpp
type KeyData (line 56) | struct KeyData
type KeyData (line 57) | struct KeyData
type KeyData (line 102) | struct KeyData
type Device (line 135) | struct Device
FILE: src/vendor/logitech/g710.hpp
class LogitechG710 (line 14) | class LogitechG710 : public Keyboard {
type Device (line 16) | struct Device
type KeyData (line 20) | struct KeyData
type KeyData (line 21) | struct KeyData
FILE: src/vendor/microsoft/sidewinder.cpp
type KeyData (line 63) | struct KeyData
type KeyData (line 64) | struct KeyData
type KeyData (line 126) | struct KeyData
type Device (line 143) | struct Device
FILE: src/vendor/microsoft/sidewinder.hpp
class SideWinder (line 14) | class SideWinder : public Keyboard {
type Device (line 16) | struct Device
type KeyData (line 20) | struct KeyData
type KeyData (line 21) | struct KeyData
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (75K chars).
[
{
"path": ".gitignore",
"chars": 26,
"preview": "build\nCMakeLists.txt.user\n"
},
{
"path": "CMakeLists.txt",
"chars": 294,
"preview": "CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8)\nPROJECT(sidewinderd)\nSET(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wex"
},
{
"path": "CONTRIBUTING",
"chars": 1422,
"preview": "Developer Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n660 Yor"
},
{
"path": "LICENSE",
"chars": 1140,
"preview": "Sidewinder daemon is made available under the MIT License.\n\nCopyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n\nPer"
},
{
"path": "README.md",
"chars": 3032,
"preview": "# Sidewinder daemon\n\nThis project provides support for gaming peripherals under Linux. It was\noriginally designed for th"
},
{
"path": "cmake/FindConfig++.cmake",
"chars": 775,
"preview": "FIND_PATH(CONFIG++_INCLUDE_DIR libconfig.h++ /usr/include /usr/local/include)\n\nFIND_LIBRARY(CONFIG++_LIBRARY NAMES confi"
},
{
"path": "cmake/FindTinyXML2.cmake",
"chars": 767,
"preview": "FIND_PATH(TINYXML2_INCLUDE_DIR tinyxml2.h /usr/include /usr/local/include)\n\nFIND_LIBRARY(TINYXML2_LIBRARY NAMES tinyxml2"
},
{
"path": "cmake/FindUDev.cmake",
"chars": 682,
"preview": "FIND_PATH(UDEV_INCLUDE_DIR libudev.h /usr/include /usr/local/include)\n\nFIND_LIBRARY(UDEV_LIBRARY NAMES udev libudev PATH"
},
{
"path": "etc/sidewinderd.conf",
"chars": 965,
"preview": "# Configuration file for sidewinderd\n\n# sidewinderd will run as the user specified here. It is not recommended to\n# leav"
},
{
"path": "etc/sidewinderd.service.in",
"chars": 209,
"preview": "[Unit]\nDescription=Support for Microsoft SideWinder X4 / X6 and Logitech G105 / G710+\nAfter=multi-user.target\n\n[Service]"
},
{
"path": "src/CMakeLists.txt",
"chars": 1040,
"preview": "INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})\n\nAUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} ROOT_SRC)\nAUX_SOURCE_"
},
{
"path": "src/core/device.hpp",
"chars": 463,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/device_manager.cpp",
"chars": 6877,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/device_manager.hpp",
"chars": 1008,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/hid_interface.cpp",
"chars": 1182,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/hid_interface.hpp",
"chars": 484,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/key.cpp",
"chars": 607,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/key.hpp",
"chars": 921,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/keyboard.cpp",
"chars": 6594,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/keyboard.hpp",
"chars": 1522,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/led.cpp",
"chars": 1767,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/led.hpp",
"chars": 1283,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/led_group.cpp",
"chars": 524,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/led_group.hpp",
"chars": 543,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/virtual_input.cpp",
"chars": 2516,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/core/virtual_input.hpp",
"chars": 958,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/device_data.hpp",
"chars": 509,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/main.cpp",
"chars": 3972,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/process.cpp",
"chars": 3831,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/process.hpp",
"chars": 944,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/vendor/logitech/g103.cpp",
"chars": 2811,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/vendor/logitech/g103.hpp",
"chars": 642,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/vendor/logitech/g105.cpp",
"chars": 4466,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/vendor/logitech/g105.hpp",
"chars": 769,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/vendor/logitech/g710.cpp",
"chars": 4522,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/vendor/logitech/g710.hpp",
"chars": 779,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/vendor/microsoft/sidewinder.cpp",
"chars": 5581,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
},
{
"path": "src/vendor/microsoft/sidewinder.hpp",
"chars": 812,
"preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
}
]
About this extraction
This page contains the full source code of the tolga9009/sidewinderd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (65.7 KB), approximately 20.5k tokens, and a symbol index with 93 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.