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 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 * * 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 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include #include 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); break; } case Device::Driver::LogitechG105: { auto keyboard = new LogitechG105(&device, &devNode, config_, process_); keyboard->connect(); connected_[device.product] = std::unique_ptr(keyboard); break; } case Device::Driver::LogitechG710: { auto keyboard = new LogitechG710(&device, &devNode, config_, process_); keyboard->connect(); connected_[device.product] = std::unique_ptr(keyboard); break; } case Device::Driver::SideWinder: { auto keyboard = new SideWinder(&device, &devNode, config_, process_); keyboard->connect(); connected_[device.product] = std::unique_ptr(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 * * 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 #include #include #include #include #include #include #include #include #include class DeviceManager { public: int monitor(); DeviceManager(libconfig::Config *config, Process *process); ~DeviceManager(); private: int fd_; std::map> connected_; std::vector 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include 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(report) << ") returned: " << std::hex << static_cast(buf[0]) << " " << static_cast(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 * * 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 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #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 * * 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 /** * 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include #include #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(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 * * 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 #include #include #include #include #include #include #include #include #include #include /* 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include 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 * * 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 #include 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include 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 * * 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 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include #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 * * 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 #include #include /** * 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 * * 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 #include namespace sidewinderd { /** * Struct for storing and passing paths to relevant /dev/ files. */ struct DevNode { std::string hidraw, inputEvent; /**< path to hidraw and input event */ }; }; #endif ================================================ FILE: src/main.cpp ================================================ /** * Copyright (c) 2014 - 2016 Tolga Cakir * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include void help(std::string name) { std::cerr << "Usage: " << name << " [options]" << std::endl << std::endl << "Options:" << std::endl << " -c, --config= 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include #include #include #include #include "process.hpp" /* constants */ constexpr auto version = "0.4.4"; constexpr auto wait = 1; std::atomic 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 * * 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 #include #include 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 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include #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(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 * * 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 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include #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(buf[1])); key = ffs(key); if (key) { keyData.index = key; keyData.type = KeyData::KeyType::Macro; } } else if (buf[1] == 0) { key = (static_cast(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 * * 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 #include 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include #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(buf[1])); key = ffs(key); if (key) { keyData.index = key; keyData.type = KeyData::KeyType::Macro; } } else if (buf[1] == 0) { key = (static_cast(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 * * 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 #include 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 * * This source file is part of Sidewinder daemon and is distributed under the * MIT License. For more information, see LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include #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(buf[1])) | (static_cast(buf[2]) << 8) | (static_cast(buf[3]) << 16) | (static_cast(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 * * 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 #include 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