[
  {
    "path": ".gitignore",
    "content": "build\nCMakeLists.txt.user\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8)\nPROJECT(sidewinderd)\nSET(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra\")\n\nADD_SUBDIRECTORY(src)\n\nSET(CMAKE_MODULE_PATH \"${CMAKE_SOURCE_DIR}/cmake\")\n\nFIND_PACKAGE(Config++ REQUIRED)\nFIND_PACKAGE(TinyXML2 REQUIRED)\nFIND_PACKAGE(UDev REQUIRED)\n"
  },
  {
    "path": "CONTRIBUTING",
    "content": "Developer Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n660 York Street, Suite 102,\nSan Francisco, CA 94110 USA\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\n\nDeveloper's Certificate of Origin 1.1\n\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I\n    have the right to submit it under the open source license\n    indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best\n    of my knowledge, is covered under an appropriate open source\n    license and I have the right under that license to submit that\n    work with modifications, whether created in whole or in part\n    by me, under the same open source license (unless I am\n    permitted to submit under a different license), as indicated\n    in the file; or\n\n(c) The contribution was provided directly to me by some other\n    person who certified (a), (b) or (c) and I have not modified\n    it.\n\n(d) I understand and agree that this project and the contribution\n    are public and that a record of the contribution (including all\n    personal information I submit with it, including my sign-off) is\n    maintained indefinitely and may be redistributed consistent with\n    this project or the open source license(s) involved.\n"
  },
  {
    "path": "LICENSE",
    "content": "Sidewinder daemon is made available under the MIT License.\n\nCopyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Sidewinder daemon\n\nThis project provides support for gaming peripherals under Linux. It was\noriginally designed for the Microsoft SideWinder X4, but we have extended\nsupport for more keyboards. Our goal is to create a framework-like environment\nfor rapid driver development under Linux.\n\n\n## Supported devices\n\n  * Microsoft SideWinder X4\n  * Microsoft SideWinder X6\n  * Logitech G103\n  * Logitech G105\n  * Logitech G710\n  * Logitech G710+\n\n\n## Install\n\nPlease check, if this project has already been packaged for your specific Linux\ndistribution. Currently maintained packages:\n\n  * Arch Linux: https://aur.archlinux.org/packages/sidewinderd/\n\nIf there are no packages available for your Linux distribution, please proceed\nwith the manual installation from source:\n\n1. Install the following dependencies. Please refer to your specific Linux\ndistribution, as package names might differ.\n\n  * cmake 2.8.8 (make)\n  * libconfig 1.4.9\n  * tinyxml2 2.2.0\n  * libudev 210\n\n2. Create a build directory in the toplevel directory:\n\n    ```\n    mkdir build\n    ```\n\n3. Run cmake from within the new build directory:\n\n    ```\n    cd build\n    cmake ..\n    ```\n\n4. Compile and install:\n\n    ```\n    make\n    make install\n    ```\n\n\n## Usage\n\nEnable and start Sidewinder daemon:\n\n    systemctl enable sidewinderd.service\n    systemctl start sidewinderd.service\n\nConfigure `/etc/sidewinderd.conf` according to your needs. Please change the\nuser, as the default user is root.\n\nYou can now use your gaming peripheral! Please note, that there is no graphical\nuser interface. Some LEDs might light up, letting you know, that Sidewinder\ndaemon has successfully recognized your keyboard.\n\n\n## Record macros\n\nThe macro keys are fully programmable, but there is no default event. You can\nadd functions to them, by recording macros or key combinations:\n\n1. Choose a profile. The profile LED on the device will show you, which profile\nis active.\n2. Press record key. The record LED will light up.\n3. Now, choose and press a macro key. On some devices, the record LED will begin\nto blink. You're now in macro mode. Please note, that existing macros may get\noverwritten.\n4. Everything you type on the keyboard will get recorded. You can either record\na single key combination or a long series of keypresses. Please note, that\nkeypresses still send events to your operating system and your programs.\n5. When done, press the record key again. This will stop recording and save the\nmacro.\n6. You've now created a macro. Use it by setting the chosen profile and pressing\nthe chosen macro key.\n\n\n## Contribution\n\nIn order to contribute to this project, you need to read and agree the Developer\nCertificate of Origin, which can be found in the CONTRIBUTING file. Therefore,\ncommits need to be signed-off. You can do this by adding the `-s` flag when\ncommiting: `git commit -s`. Pseudonyms and anonymous contributions will not be\naccepted.\n\n\n## License\n\nThis project is made available under the MIT License. For more information,\nplease refer to the LICENSE file.\n"
  },
  {
    "path": "cmake/FindConfig++.cmake",
    "content": "FIND_PATH(CONFIG++_INCLUDE_DIR libconfig.h++ /usr/include /usr/local/include)\n\nFIND_LIBRARY(CONFIG++_LIBRARY NAMES config++ PATH /usr/lib /usr/local/lib)\n\nIF(CONFIG++_INCLUDE_DIR AND CONFIG++_LIBRARY)\n\tSET(CONFIG++_FOUND TRUE)\nENDIF(CONFIG++_INCLUDE_DIR AND CONFIG++_LIBRARY)\n\nIF(CONFIG++_FOUND)\n\tIF(NOT CONFIG++_FIND_QUIETLY)\n\t\tMESSAGE(STATUS \"Found Config++: ${CONFIG++_LIBRARY}\")\n\tENDIF(NOT CONFIG++_FIND_QUIETLY)\nELSE(CONFIG++_FOUND)\n\tIF(CONFIG++_FIND_REQUIRED)\n\t\tIF(NOT CONFIG++_INCLUDE_DIR)\n\t\t\tMESSAGE(FATAL_ERROR \"Could not find LibConfig++ header file!\")\n\t\tENDIF(NOT CONFIG++_INCLUDE_DIR)\n\n\t\tIF(NOT CONFIG++_LIBRARY)\n\t\t\tMESSAGE(FATAL_ERROR \"Could not find LibConfig++ library file!\")\n\t\tENDIF(NOT CONFIG++_LIBRARY)\n\tENDIF(CONFIG++_FIND_REQUIRED)\nENDIF(CONFIG++_FOUND)\n"
  },
  {
    "path": "cmake/FindTinyXML2.cmake",
    "content": "FIND_PATH(TINYXML2_INCLUDE_DIR tinyxml2.h /usr/include /usr/local/include)\n\nFIND_LIBRARY(TINYXML2_LIBRARY NAMES tinyxml2 PATH /usr/lib /usr/local/lib)\n\nIF(TINYXML2_INCLUDE_DIR AND TINYXML2_LIBRARY)\n\tSET(TINYXML2_FOUND TRUE)\nENDIF(TINYXML2_INCLUDE_DIR AND TINYXML2_LIBRARY)\n\nIF(TINYXML2_FOUND)\n\tIF(NOT TINYXML2_FIND_QUIETLY)\n\t\tMESSAGE(STATUS \"Found TinyXML2: ${TINYXML2_LIBRARY}\")\n\tENDIF(NOT TINYXML2_FIND_QUIETLY)\nELSE(TINYXML2_FOUND)\n\tIF(TINYXML2_FIND_REQUIRED)\n\t\tIF(NOT TINYXML2_INCLUDE_DIR)\n\t\t\tMESSAGE(FATAL_ERROR \"Could not find TinyXML2 header file!\")\n\t\tENDIF(NOT TINYXML2_INCLUDE_DIR)\n\n\t\tIF(NOT TINYXML2_LIBRARY)\n\t\t\tMESSAGE(FATAL_ERROR \"Could not find TinyXML2 library files!\")\n\t\tENDIF(NOT TINYXML2_LIBRARY)\n\tENDIF(TINYXML2_FIND_REQUIRED)\nENDIF(TINYXML2_FOUND)\n"
  },
  {
    "path": "cmake/FindUDev.cmake",
    "content": "FIND_PATH(UDEV_INCLUDE_DIR libudev.h /usr/include /usr/local/include)\n\nFIND_LIBRARY(UDEV_LIBRARY NAMES udev libudev PATH /usr/lib /usr/local/lib)\n\nIF(UDEV_INCLUDE_DIR AND UDEV_LIBRARY)\n\tSET(UDEV_FOUND TRUE)\nENDIF(UDEV_INCLUDE_DIR AND UDEV_LIBRARY)\n\nIF(UDEV_FOUND)\n\tIF(NOT UDEV_FIND_QUIETLY)\n\t\tMESSAGE(STATUS \"Found UDev: ${UDEV_LIBRARY}\")\n\tENDIF(NOT UDEV_FIND_QUIETLY)\nELSE(UDEV_FOUND)\n\tIF(UDEV_FIND_REQUIRED)\n\t\tIF(NOT UDEV_INCLUDE_DIR)\n\t\t\tMESSAGE(FATAL_ERROR \"Could not find UDev header file!\")\n\t\tENDIF(NOT UDEV_INCLUDE_DIR)\n\n\t\tIF(NOT UDEV_LIBRARY)\n\t\t\tMESSAGE(FATAL_ERROR \"Could not find UDev library files!\")\n\t\tENDIF(NOT UDEV_LIBRARY)\n\tENDIF(UDEV_FIND_REQUIRED)\nENDIF(UDEV_FOUND)\n"
  },
  {
    "path": "etc/sidewinderd.conf",
    "content": "# Configuration file for sidewinderd\n\n# sidewinderd will run as the user specified here. It is not recommended to\n# leave it at default value \"root\".\nuser = \"root\";\n\n# If set to false, macro recording will not capture any delays.\ncapture_delays = true;\n\n# Change the PID file path here, if you experience issues with the default path.\npid-file = \"/var/run/sidewinderd.pid\";\n\n# Defines, whether sidewinderd should wait for the working directory to become\n# available or not.\n# If set to true, sidewinderd will infinitely poll and wait, until the working\n# directory becomes available. This is needed in environments, where the\n# specified user's home directory is encrypted and not available on boot.\n#encrypted_workdir = false;\n\n# You can set an alternative profile path here.\n# If this setting is set, sidewinderd will no longer use your specified user's\n# home directory for storing application data, but instead use this path.\n#workdir = \"/var/lib/sidewinderd\";\n"
  },
  {
    "path": "etc/sidewinderd.service.in",
    "content": "[Unit]\nDescription=Support for Microsoft SideWinder X4 / X6 and Logitech G105 / G710+\nAfter=multi-user.target\n\n[Service]\nExecStart=${CMAKE_INSTALL_PREFIX}/bin/sidewinderd\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})\n\nAUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} ROOT_SRC)\nAUX_SOURCE_DIRECTORY(\"${CMAKE_CURRENT_SOURCE_DIR}/core\" CORE_SRC)\nAUX_SOURCE_DIRECTORY(\"${CMAKE_CURRENT_SOURCE_DIR}/vendor/logitech\" LOGITECH_SRC)\nAUX_SOURCE_DIRECTORY(\"${CMAKE_CURRENT_SOURCE_DIR}/vendor/microsoft\" MICROSOFT_SRC)\nLIST(APPEND VENDOR_LIST ${LOGITECH_SRC} ${MICROSOFT_SRC})\nLIST(APPEND SOURCE_LIST ${ROOT_SRC} ${CORE_SRC} ${VENDOR_LIST})\n\nCONFIGURE_FILE(\"${PROJECT_SOURCE_DIR}/etc/sidewinderd.service.in\" \"${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service\")\n\nADD_EXECUTABLE(${PROJECT_NAME} ${SOURCE_LIST} \"${PROJECT_SOURCE_DIR}/etc/sidewinderd.conf\" \"${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service\")\n\nTARGET_LINK_LIBRARIES(${PROJECT_NAME} stdc++ config++ udev pthread tinyxml2)\n\nINSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin)\nINSTALL(FILES \"${PROJECT_SOURCE_DIR}/etc/sidewinderd.conf\" DESTINATION /etc COMPONENT config)\nINSTALL(FILES \"${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service\" DESTINATION lib/systemd/system)\n"
  },
  {
    "path": "src/core/device.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef DEVICE_CLASS_H\n#define DEVICE_CLASS_H\n\n#include <string>\n\nstruct Device {\n\t\tstd::string vendor;\n\t\tstd::string product;\n\t\tstd::string name;\n\n\t\tenum class Driver {\n\t\t\tLogitechG103,\n\t\t\tLogitechG105,\n\t\t\tLogitechG710,\n\t\t\tSideWinder\n\t\t} driver;\n};\n\n#endif\n"
  },
  {
    "path": "src/core/device_manager.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <cstring>\n#include <iostream>\n\n#include <core/device_manager.hpp>\n#include <vendor/logitech/g103.hpp>\n#include <vendor/logitech/g105.hpp>\n#include <vendor/logitech/g710.hpp>\n#include <vendor/microsoft/sidewinder.hpp>\n\nconstexpr auto VENDOR_MICROSOFT =\t\"045e\";\nconstexpr auto VENDOR_LOGITECH =\t\"046d\";\nconstexpr auto TIMEOUT =\t\t5000;\nconstexpr auto NUM_POLL =\t\t1;\n\nvoid DeviceManager::discover() {\n\tfor (auto it : devices_) {\n\t\tstruct Device device = it;\n\t\tstruct sidewinderd::DevNode devNode;\n\n\t\tif (probe(&device, &devNode) > 0) {\n\t\t\t// TODO use a unique identifier\n\t\t\tauto it = connected_.find(device.product);\n\n\t\t\t// skip this loop, if device is already connected\n\t\t\tif (it != connected_.end()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tswitch (device.driver) {\n\t\t\t\tcase Device::Driver::LogitechG103: {\n\t\t\t\t\tauto keyboard = new LogitechG103(&device, &devNode, config_, process_);\n\t\t\t\t\tkeyboard->connect();\n\t\t\t\t\tconnected_[device.product] = std::unique_ptr<Keyboard>(keyboard);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase Device::Driver::LogitechG105: {\n\t\t\t\t\tauto keyboard = new LogitechG105(&device, &devNode, config_, process_);\n\t\t\t\t\tkeyboard->connect();\n\t\t\t\t\tconnected_[device.product] = std::unique_ptr<Keyboard>(keyboard);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase Device::Driver::LogitechG710: {\n\t\t\t\t\tauto keyboard = new LogitechG710(&device, &devNode, config_, process_);\n\t\t\t\t\tkeyboard->connect();\n\t\t\t\t\tconnected_[device.product] = std::unique_ptr<Keyboard>(keyboard);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase Device::Driver::SideWinder: {\n\t\t\t\t\tauto keyboard = new SideWinder(&device, &devNode, config_, process_);\n\t\t\t\t\tkeyboard->connect();\n\t\t\t\t\tconnected_[device.product] = std::unique_ptr<Keyboard>(keyboard);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nint DeviceManager::monitor() {\n\t// create udev object\n\tudev_ = udev_new();\n\n\tif (!udev_) {\n\t\tstd::cerr << \"Can't create udev.\" << std::endl;\n\n\t\treturn -1;\n\t}\n\n\t// set up monitor for /dev/hidraw and /dev/input/event*\n\tmonitor_ = udev_monitor_new_from_netlink(udev_, \"udev\");\n\tudev_monitor_filter_add_match_subsystem_devtype(monitor_, \"hidraw\", NULL);\n\tudev_monitor_filter_add_match_subsystem_devtype(monitor_, \"input\", NULL);\n\tudev_monitor_enable_receiving(monitor_);\n\n\t// get file descriptor for the monitor, so we can use poll() on it\n\tfd_ = udev_monitor_get_fd(monitor_);\n\n\t// setup poll\n\tpfd_.fd = fd_;\n\tpfd_.events = POLLIN;\n\n\t// initial discovery of new devices\n\tdiscover();\n\n\tstruct udev_device *dev;\n\n\t// run monitoring loop, until we receive a signal\n\twhile (process_->isActive()) {\n\t\tpoll(&pfd_, NUM_POLL, TIMEOUT);\n\t\tdev = udev_monitor_receive_device(monitor_);\n\n\t\tif (dev) {\n\t\t\t// filter out nullptr returns, else std::string() fails\n\t\t\tauto ret = udev_device_get_action(dev);\n\n\t\t\tif (ret) {\n\t\t\t\tstd::string action(ret);\n\n\t\t\t\tif (action == \"add\") {\n\t\t\t\t\tdiscover();\n\t\t\t\t} else if (action == \"remove\") {\n\t\t\t\t\t// check for disconnected devices\n\t\t\t\t\tauto product = udev_device_get_property_value(dev, \"ID_MODEL_ID\");\n\n\t\t\t\t\tif (product) {\n\t\t\t\t\t\tunbind(product);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tudev_device_unref(dev);\n\t\t}\n\t}\n\n\tudev_monitor_unref(monitor_);\n\tudev_unref(udev_);\n\n\treturn 0;\n}\n\nint DeviceManager::probe(struct Device *device, struct sidewinderd::DevNode *devNode) {\n\tstruct udev_device *dev;\n\tstruct udev_enumerate *enumerate;\n\tstruct udev_list_entry *devices, *entry;\n\tbool isFound = false;\n\n\t// create a list of devices in hidraw and input subsystems\n\tenumerate = udev_enumerate_new(udev_);\n\tudev_enumerate_add_match_subsystem(enumerate, \"hidraw\");\n\tudev_enumerate_add_match_subsystem(enumerate, \"input\");\n\tudev_enumerate_scan_devices(enumerate);\n\tdevices = udev_enumerate_get_list_entry(enumerate);\n\n\tudev_list_entry_foreach(entry, devices) {\n\t\tconst char *sysPath, *devNodePath;\n\t\tsysPath = udev_list_entry_get_name(entry);\n\t\tdev = udev_device_new_from_syspath(udev_, sysPath);\n\t\tauto hidraw = udev_device_get_subsystem(dev);\n\n\t\t// evaluation from left to right; used to filter out nullptr\n\t\tif (hidraw && std::string(hidraw) == std::string(\"hidraw\")) {\n\t\t\tdevNodePath = udev_device_get_devnode(dev);\n\t\t\tdev = udev_device_get_parent_with_subsystem_devtype(dev, \"usb\", \"usb_interface\");\n\n\t\t\tif (!dev) {\n\t\t\t\tstd::cerr << \"Unable to find parent device.\" << std::endl;\n\t\t\t}\n\n\t\t\tauto bInterfaceNumber = udev_device_get_sysattr_value(dev, \"bInterfaceNumber\");\n\n\t\t\tif (bInterfaceNumber && std::string(bInterfaceNumber) == std::string(\"01\")) {\n\t\t\t\tdev = udev_device_get_parent_with_subsystem_devtype(dev, \"usb\", \"usb_device\");\n\t\t\t\tauto idVendor = udev_device_get_sysattr_value(dev, \"idVendor\");\n\t\t\t\tauto idProduct = udev_device_get_sysattr_value(dev, \"idProduct\");\n\n\t\t\t\tif (idVendor && std::string(idVendor) == device->vendor\n\t\t\t\t\t&& idProduct && std::string(idProduct) == device->product) {\n\t\t\t\t\t\tdevNode->hidraw = devNodePath;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tauto input = udev_device_get_subsystem(dev);\n\t\tauto product = udev_device_get_property_value(dev, \"ID_MODEL_ID\");\n\t\tauto vendor = udev_device_get_property_value(dev, \"ID_VENDOR_ID\");\n\t\tauto interface = udev_device_get_property_value(dev, \"ID_USB_INTERFACE_NUM\");\n\n\t\t/* find correct /dev/input/event* file */\n\t\tif (input && std::string(input) == std::string(\"input\")\n\t\t\t&& product && std::string(product) == device->product\n\t\t\t&& vendor && std::string(vendor) == device->vendor\n\t\t\t&& interface && std::string(interface) == \"00\"\n\t\t\t&& udev_device_get_property_value(dev, \"ID_INPUT_KEYBOARD\")\n\t\t\t&& strstr(sysPath, \"event\")\n\t\t\t&& udev_device_get_parent_with_subsystem_devtype(dev, \"usb\", NULL)) {\n\t\t\t\tdevNode->inputEvent = udev_device_get_devnode(dev);\n\t\t\t\tstd::clog << \"Found device: \" << device->vendor << \":\" << device->product << std::endl;\n\t\t\t\tisFound = true;\n\t\t}\n\n\t\tudev_device_unref(dev);\n\t}\n\n\t/* free the enumerator object */\n\tudev_enumerate_unref(enumerate);\n\n\treturn isFound;\n}\n\nvoid DeviceManager::unbind(std::string product) {\n\tauto it = connected_.find(product);\n\n\tif (it != connected_.end() && it->second->isConnected()) {\n\t\tconnected_.erase(product);\n\t}\n}\n\nDeviceManager::DeviceManager(libconfig::Config *config, Process *process) {\n\t// list of supported devices\n\tdevices_ = {\n\t\t{VENDOR_MICROSOFT, \"074b\", \"Microsoft SideWinder X6\",\n\t\t\tDevice::Driver::SideWinder},\n\t\t{VENDOR_MICROSOFT, \"0768\", \"Microsoft SideWinder X4\",\n\t\t\tDevice::Driver::SideWinder},\n\t\t{VENDOR_LOGITECH, \"c248\", \"Logitech G105\",\n\t\t\tDevice::Driver::LogitechG105},\n\t\t{VENDOR_LOGITECH, \"c24b\", \"Logitech G103\",\n\t\t\tDevice::Driver::LogitechG103},\n\t\t{VENDOR_LOGITECH, \"c24d\", \"Logitech G710+\",\n\t\t\tDevice::Driver::LogitechG710}\n\t};\n\n\tconfig_ = config;\n\tprocess_ = process;\n\tudev_ = nullptr;\n\tmonitor_ = nullptr;\n}\n\nDeviceManager::~DeviceManager() {\n\t// remove all connected devices\n\tconnected_.clear();\n\n\tif (udev_) {\n\t\tudev_unref(udev_);\n\t}\n}\n"
  },
  {
    "path": "src/core/device_manager.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef DEVICE_MANAGER_CLASS_H\n#define DEVICE_MANAGER_CLASS_H\n\n#include <map>\n#include <string>\n#include <vector>\n\n#include <libudev.h>\n#include <poll.h>\n\n#include <libconfig.h++>\n\n#include <device_data.hpp>\n#include <process.hpp>\n#include <core/device.hpp>\n#include <core/keyboard.hpp>\n\nclass DeviceManager {\n\tpublic:\n\t\tint monitor();\n\t\tDeviceManager(libconfig::Config *config, Process *process);\n\t\t~DeviceManager();\n\n\tprivate:\n\t\tint fd_;\n\t\tstd::map<std::string, std::unique_ptr<Keyboard>> connected_;\n\t\tstd::vector<Device> devices_;\n\t\tstruct pollfd pfd_;\n\t\tstruct udev *udev_;\n\t\tstruct udev_monitor *monitor_;\n\t\tlibconfig::Config *config_;\n\t\tProcess *process_;\n\t\tvoid discover();\n\t\tint probe(struct Device *device, struct sidewinderd::DevNode *devNode);\n\t\tvoid unbind(std::string product);\n};\n\n#endif\n"
  },
  {
    "path": "src/core/hid_interface.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <iostream>\n\n#include <linux/hidraw.h>\n\n#include <sys/ioctl.h>\n\n#include <core/hid_interface.hpp>\n\nunsigned char HidInterface::getReport(unsigned char report) {\n\tunsigned char buf[2] {};\n\tbuf[0] = report;\n\tint ret = ioctl(*fd_, HIDIOCGFEATURE(sizeof(buf)), buf);\n\n\tif (ret < 0) {\n\t\tstd::cerr << \"Error getting HID feature report.\" << std::endl;\n\t} else {\n\t\tstd::cout << \"getFeatureReport(\" << static_cast<int>(report)\n\t\t\t  << \") returned: \" << std::hex\n\t\t\t  << static_cast<int>(buf[0]) << \" \"\n\t\t\t  << static_cast<int>(buf[1]) <<  std::endl;\n\t}\n\n\treturn buf[1];\n}\n\nvoid HidInterface::setReport(unsigned char report, unsigned char value) {\n\tunsigned char buf[2];\n\t/* buf[0] is Report ID, buf[1] is value */\n\tbuf[0] = report;\n\tbuf[1] = value;\n\t/* TODO: check return value */\n\tint ret = ioctl(*fd_, HIDIOCSFEATURE(sizeof(buf)), buf);\n\n\tif (ret < 0) {\n\t\tstd::cerr << \"Error setting HID feature report.\" << std::endl;\n\t}\n}\n\nHidInterface::HidInterface(int *fd) {\n\tfd_ = fd;\n}\n"
  },
  {
    "path": "src/core/hid_interface.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef HID_INTERFACE_CLASS_H\n#define HID_INTERFACE_CLASS_H\n\n#include <string>\n\nclass HidInterface {\n\tpublic:\n\t\tunsigned char getReport(unsigned char report);\n\t\tvoid setReport(unsigned char report, unsigned char value);\n\t\tHidInterface(int *fd);\n\n\tprivate:\n\t\tint *fd_;\n};\n\n#endif\n"
  },
  {
    "path": "src/core/key.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <iostream>\n#include <sstream>\n\n#include \"key.hpp\"\n\n/**\n * Assembles relative path to Macro file.\n */\nstd::string Key::getMacroPath(int profile) {\n\tstd::stringstream macroPath;\n\tmacroPath << \"profile_\" << profile + 1 << \"/\" << \"s\" << keyData_->index << \".xml\";\n\n\treturn macroPath.str();\n}\n\n/**\n * Constructor initializing structs.\n */\nKey::Key(struct KeyData *keyData) {\n\tkeyData_ = keyData;\n}\n"
  },
  {
    "path": "src/core/key.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef KEY_CLASS_H\n#define KEY_CLASS_H\n\n#include <string>\n\n/**\n * Struct for storing and passing key data.\n *\n * @var index key index\n */\nstruct KeyData {\n\tint index;\n\n\t/**\n\t * Enum class to classify key type.\n\t * \n\t * @var Unknown not classified type\n\t * @var Macro key type for macro keys, e.g. S1 on Microsoft Sidewinder X4\n\t * @var Extra key type for extra keys, e.g. Bank Switch key on Microsoft\n\t * Sidewinder X4\n\t */\n\tenum class KeyType {\n\t\tUnknown,\n\t\tMacro,\n\t\tExtra\n\t} type;\n};\n\n/**\n * Class representing a key.\n *\n * Currently, it only works for Macro keys.\n */\nclass Key {\n\tpublic:\n\t\tstd::string getMacroPath(int profile);\n\t\tKey(struct KeyData *keyData);\n\n\tprivate:\n\t\tstruct KeyData *keyData_;\n};\n\n#endif\n"
  },
  {
    "path": "src/core/keyboard.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <cstdio>\n#include <ctime>\n#include <iostream>\n#include <sstream>\n#include <thread>\n\n#include <fcntl.h>\n#include <tinyxml2.h>\n#include <unistd.h>\n\n#include <linux/hidraw.h>\n#include <linux/input.h>\n\n#include <sys/ioctl.h>\n#include <sys/stat.h>\n\n#include \"keyboard.hpp\"\n\nconstexpr auto TIMEOUT =\t5000;\n\nbool Keyboard::isConnected() {\n\treturn isConnected_;\n}\n\nvoid Keyboard::connect() {\n\tisConnected_ = true;\n\tlistenThread_ = std::thread(&Keyboard::listen, this);\n}\n\nvoid Keyboard::disconnect() {\n\tisConnected_ = false;\n}\n\nvoid Keyboard::setupPoll() {\n\tfds[0].fd = fd_;\n\tfds[0].events = POLLIN;\n\t/* ignore second fd for now */\n\tfds[1].fd = -1;\n\tfds[1].events = POLLIN;\n}\n\n/* TODO: interrupt and exit play_macro when any macro_key has been pressed */\nvoid Keyboard::playMacro(std::string macroPath, VirtualInput *virtInput) {\n\ttinyxml2::XMLDocument xmlDoc;\n\txmlDoc.LoadFile(macroPath.c_str());\n\n\tif(!xmlDoc.ErrorID()) {\n\t\ttinyxml2::XMLElement* root = xmlDoc.FirstChildElement(\"Macro\");\n\n\t\tfor (tinyxml2::XMLElement* child = root->FirstChildElement(); child; child = child->NextSiblingElement()) {\n\t\t\tif (child->Name() == std::string(\"KeyBoardEvent\")) {\n\t\t\t\tbool isPressed;\n\t\t\t\tint key = std::atoi(child->GetText());\n\t\t\t\tchild->QueryBoolAttribute(\"Down\", &isPressed);\n\t\t\t\tvirtInput->sendEvent(EV_KEY, key, isPressed);\n\t\t\t} else if (child->Name() == std::string(\"DelayEvent\")) {\n\t\t\t\tint delay = std::atoi(child->GetText());\n\t\t\t\tstruct timespec request, remain;\n\t\t\t\t/*\n\t\t\t\t * value is given in milliseconds, so we need to split it into\n\t\t\t\t * seconds and nanoseconds. nanosleep() is interruptable and saves\n\t\t\t\t * the remaining sleep time.\n\t\t\t\t */\n\t\t\t\trequest.tv_sec = delay / 1000;\n\t\t\t\tdelay = delay - (request.tv_sec * 1000);\n\t\t\t\trequest.tv_nsec = 1000000L * delay;\n\t\t\t\tnanosleep(&request, &remain);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n * Macro recording captures delays by default. Use the configuration to disable\n * capturing delays.\n */\nvoid Keyboard::recordMacro(std::string path, Led *ledRecord, const int keyRecord) {\n\tstruct timeval prev;\n\tstruct KeyData keyData;\n\tprev.tv_usec = 0;\n\tprev.tv_sec = 0;\n\tstd::cout << \"Start Macro Recording on \" << devNode_.inputEvent << std::endl;\n\tprocess_->privilege();\n\tevfd_ = open(devNode_.inputEvent.c_str(), O_RDONLY | O_NONBLOCK);\n\tprocess_->unprivilege();\n\n\tif (evfd_ < 0) {\n\t\tstd::cout << \"Can't open input event file\" << std::endl;\n\t}\n\n\t/* additionally monitor /dev/input/event* with poll */\n\tfds[1].fd = evfd_;\n\ttinyxml2::XMLDocument doc;\n\ttinyxml2::XMLNode* root = doc.NewElement(\"Macro\");\n\t/* start root element \"Macro\" */\n\tdoc.InsertFirstChild(root);\n\n\tbool isRecordMode = true;\n\n\twhile (isRecordMode) {\n\t\tkeyData = pollDevice(2);\n\n\t\tif (keyData.index == keyRecord && keyData.type == KeyData::KeyType::Extra) {\n\t\t\tledRecord->off();\n\t\t\tisRecordMode = false;\n\t\t}\n\n\t\tstruct input_event inev;\n\t\tread(evfd_, &inev, sizeof(struct input_event));\n\n\t\tif (inev.type == EV_KEY && inev.value != 2) {\n\t\t\t/* only capturing delays, if capture_delays is set to true */\n\t\t\tif (prev.tv_usec && config_->lookup(\"capture_delays\")) {\n\t\t\t\tauto diff = (inev.time.tv_usec + 1000000 * inev.time.tv_sec) - (prev.tv_usec + 1000000 * prev.tv_sec);\n\t\t\t\tauto delay = diff / 1000;\n\t\t\t\t/* start element \"DelayEvent\" */\n\t\t\t\ttinyxml2::XMLElement* DelayEvent = doc.NewElement(\"DelayEvent\");\n\t\t\t\tDelayEvent->SetText(static_cast<int>(delay));\n\t\t\t\troot->InsertEndChild(DelayEvent);\n\t\t\t}\n\n\t\t\t/* start element \"KeyBoardEvent\" */\n\t\t\ttinyxml2::XMLElement* KeyBoardEvent = doc.NewElement(\"KeyBoardEvent\");\n\n\t\t\tif (inev.value) {\n\t\t\t\tKeyBoardEvent->SetAttribute(\"Down\", true);\n\t\t\t} else {\n\t\t\t\tKeyBoardEvent->SetAttribute(\"Down\", false);\n\t\t\t}\n\n\t\t\tKeyBoardEvent->SetText(inev.code);\n\t\t\troot->InsertEndChild(KeyBoardEvent);\n\t\t\tprev = inev.time;\n\t\t}\n\t}\n\n\t/* write XML document */\n\tif (doc.SaveFile(path.c_str())) {\n\t\tstd::cout << \"Error XML SaveFile\" << std::endl;\n\t}\n\n\tstd::cout << \"Exit Macro Recording\" << std::endl;\n\t/* remove event file from poll fds */\n\tfds[1].fd = -1;\n\tclose(evfd_);\n}\n\nstruct KeyData Keyboard::pollDevice(nfds_t nfds) {\n\t/*\n\t * poll() checks the device for any activities and blocks the loop,\n\t * either until an event has occured, or the timeout has been reached.\n\t * This leads to a very efficient polling mechanism.\n\t */\n\tpoll(fds, nfds, TIMEOUT);\n\n\t// check, if device has been disconnected\n\tif (fds->revents & POLLHUP || fds->revents & POLLERR) {\n\t\tdisconnect();\n\n\t\treturn KeyData();\n\t}\n\n\tstruct KeyData keyData = getInput();\n\n\treturn keyData;\n}\n\nvoid Keyboard::listen() {\n\twhile (process_->isActive() && isConnected()) {\n\t\tstruct KeyData keyData = pollDevice(1);\n\t\thandleKey(&keyData);\n\t}\n}\n\nvoid Keyboard::handleRecordMode(Led *ledRecord, const int keyRecord) {\n\tbool isRecordMode = true;\n\t/* record LED solid light */\n\tledRecord->on();\n\n\twhile (isRecordMode) {\n\t\tstruct KeyData keyData = pollDevice(1);\n\n\t\tif (keyData.type == KeyData::KeyType::Unknown\n\t\t\t\t|| !keyData.index) {\n\t\t\t/* skip iteration if event is unknown or index is 0 */\n\t\t\tcontinue;\n\t\t} else if (keyData.type == KeyData::KeyType::Macro) {\n\t\t\t/* record LED should blink */\n\t\t\tledRecord->blink();\n\t\t\tisRecordMode = false;\n\t\t\tKey key(&keyData);\n\t\t\trecordMacro(key.getMacroPath(profile_), ledRecord, keyRecord);\n\t\t} else if (keyData.type == KeyData::KeyType::Extra) {\n\t\t\t/* deactivate Record LED */\n\t\t\tledRecord->off();\n\t\t\tisRecordMode = false;\n\n\t\t\tif (keyData.index != keyRecord) {\n\t\t\t\thandleKey(&keyData);\n\t\t\t}\n\t\t}\n\t}\n}\n\nKeyboard::Keyboard(struct Device *device,\n\t\tsidewinderd::DevNode *devNode, libconfig::Config *config,\n\t\tProcess *process) : hid_{&fd_} {\n\tconfig_ = config;\n\tprocess_ = process;\n\tdevice_ = *device;\n\tdevNode_ = *devNode;\n\tvirtInput_ = new VirtualInput(&device_, &devNode_, process_);\n\tprofile_ = 0;\n\tisConnected_ = true;\n\n\tfor (int i = MIN_PROFILE; i < MAX_PROFILE; i++) {\n\t\tstd::stringstream profileFolderPath;\n\t\tprofileFolderPath << \"profile_\" << i + 1;\n\t\tmkdir(profileFolderPath.str().c_str(), S_IRWXU);\n\t}\n\n\t/* open file descriptor with root privileges */\n\tprocess_->privilege();\n\tfd_ = open(devNode->hidraw.c_str(), O_RDWR | O_NONBLOCK);\n\tprocess_->unprivilege();\n\n\t/* TODO: destruct, if interface can't be accessed */\n\tif (fd_ < 0) {\n\t\tstd::cout << \"Can't open hidraw interface\" << std::endl;\n\t}\n\n\tsetupPoll();\n\n\tstd::cerr << \"Keyboard Constructor\" << std::endl;\n}\n\nKeyboard::~Keyboard() {\n\tstd::cerr << \"Keyboard Destructor\" << std::endl;\n\n\tdelete virtInput_;\n\tclose(fd_);\n}\n"
  },
  {
    "path": "src/core/keyboard.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef KEYBOARD_CLASS_H\n#define KEYBOARD_CLASS_H\n\n#include <string>\n#include <thread>\n\n#include <poll.h>\n\n#include <libconfig.h++>\n\n#include <process.hpp>\n#include <device_data.hpp>\n#include <core/device.hpp>\n#include <core/hid_interface.hpp>\n#include <core/key.hpp>\n#include <core/led.hpp>\n#include <core/virtual_input.hpp>\n\n/* constants */\nconst int MAX_BUF = 8;\nconst int MIN_PROFILE = 0;\nconst int MAX_PROFILE = 3;\n\nclass Keyboard {\n\tpublic:\n\t\tbool isConnected();\n\t\tvoid connect();\n\t\tvoid disconnect();\n\t\tvoid listen();\n\t\tKeyboard(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);\n\t\tvirtual ~Keyboard();\n\n\tprotected:\n\t\tbool isConnected_;\n\t\tint profile_;\n\t\tint fd_, evfd_;\n\t\tstd::thread listenThread_;\n\t\tProcess *process_;\n\t\tstruct pollfd fds[2];\n\t\tstruct Device device_;\n\t\tlibconfig::Config *config_;\n\t\tsidewinderd::DevNode devNode_;\n\t\tHidInterface hid_;\n\t\tVirtualInput *virtInput_;\n\t\tvirtual struct KeyData getInput() = 0;\n\t\tvoid setupPoll();\n\t\tstatic void playMacro(std::string macroPath, VirtualInput *virtInput);\n\t\tvoid recordMacro(std::string path, Led *ledRecord, const int keyRecord);\n\t\tstruct KeyData pollDevice(nfds_t nfds);\n\t\tvirtual void handleKey(struct KeyData *keyData) = 0;\n\t\tvoid handleRecordMode(Led *ledRecord, const int keyRecord);\n};\n\n#endif\n"
  },
  {
    "path": "src/core/led.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <iostream>\n#include <core/led.hpp>\n\nvoid Led::on() {\n\tauto report = hid_->getReport(report_);\n\tauto buf = report;\n\n\tif (type_ == LedType::Profile) {\n\t\t// clear out all LEDs, but Indicator LEDs\n\t\tbuf &= group_->getIndicatorMask();\n\t}\n\n\tbuf |= led_;\n\n\t// don't call, if there are no changes\n\tif (buf != report) {\n\t\thid_->setReport(report_, buf);\n\t}\n}\n\nvoid Led::off() {\n\tauto buf = hid_->getReport(report_);\n\tbuf &= ~led_;\n\thid_->setReport(report_, buf);\n}\n\nvoid Led::blink() {\n\tif (blink_) {\n\t\tauto buf = hid_->getReport(report_);\n\t\tbuf &= ~led_;\n\t\tbuf |= blink_;\n\t\thid_->setReport(report_, buf);\n\t} else {\n\t\t/*\n\t\t * TODO Implement non-blocking software emulated blink, using\n\t\t * std::thread and std::chrono. It should start blinking, when\n\t\t * Led::blink() has been called and stop, when Led::off() has\n\t\t * been called. 1 second on, 1 second off sounds like a good\n\t\t * rhythm. It would be perfect, if one could interrupt the blink\n\t\t * anytime, even in a this_thread::wait_for().\n\t\t * Until it's implemented, let's use a solid light.\n\t\t */\n\t\ton();\n\t}\n}\n\nvoid Led::registerBlink(unsigned char led) {\n\tblink_ = led;\n}\n\nvoid Led::setLedType(LedType type) {\n\ttype_ = type;\n\n\tif (type_ == LedType::Indicator) {\n\t\tauto indicator= group_->getIndicatorMask();\n\t\tindicator |= led_;\n\t\tgroup_->setIndicatorMask(indicator);\n\t}\n}\n\nLed::Led(unsigned char report, unsigned char led, LedGroup *group) {\n\treport_ = report;\n\tled_ = led;\n\tgroup_ = group;\n\tblink_ = 0;\n\ttype_ = LedType::Common;\n\thid_ = group_->getHidInterface();\n\n\t// initial LED state is off\n\toff();\n}\n"
  },
  {
    "path": "src/core/led.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef LED_CLASS_H\n#define LED_CLASS_H\n\n#include <core/hid_interface.hpp>\n#include <core/led_group.hpp>\n\nenum class LedType {\n\tCommon,\t\t// common LEDs don't have special handling\n\tProfile,\t// profile LEDs are exclusively lit\n\tIndicator\t// indicator LEDs are not allowed to be overwritten\n};\n\nclass Led {\n\tpublic:\n\t\t/**\n\t\t * Turns LED on.\n\t\t */\n\t\tvoid on();\n\n\t\t/**\n\t\t * Turns LED off.\n\t\t */\n\t\tvoid off();\n\n\t\t/**\n\t\t * Sets LED to blinking mode. Falls back to software emulation,\n\t\t * if there is no hardware support.\n\t\t */\n\t\tvoid blink();\n\n\t\t/**\n\t\t * If LED supports blinking via hardware, set report ID and\n\t\t * value using this function.\n\t\t * @param led blink specific HID report value\n\t\t */\n\t\tvoid registerBlink(unsigned char led);\n\n\t\t/**\n\t\t * Sets LedType.\n\t\t * @param type LedType can be Common, Profile or Indicator.\n\t\t */\n\t\tvoid setLedType(LedType type);\n\t\tLed(unsigned char report, unsigned char led, LedGroup *group);\n\n\tprivate:\n\t\tunsigned char report_;\n\t\tunsigned char led_;\n\t\tunsigned char blink_;\n\t\tLedGroup *group_;\n\t\tLedType type_;\n\t\tHidInterface *hid_;\n};\n\n#endif\n"
  },
  {
    "path": "src/core/led_group.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <core/led_group.hpp>\n\nunsigned char LedGroup::getIndicatorMask() {\n\treturn indicator_;\n}\n\nvoid LedGroup::setIndicatorMask(unsigned char indicator) {\n\tindicator_ = indicator;\n}\n\nHidInterface *LedGroup::getHidInterface() {\n\treturn hid_;\n}\n\nLedGroup::LedGroup(HidInterface *hid) {\n\thid_ = hid;\n\tindicator_ = 0;\n}\n"
  },
  {
    "path": "src/core/led_group.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef LED_GROUP_CLASS_H\n#define LED_GROUP_CLASS_H\n\n#include <core/hid_interface.hpp>\n\nclass LedGroup {\n\tpublic:\n\t\tunsigned char getIndicatorMask();\n\t\tvoid setIndicatorMask(unsigned char indicator);\n\t\tHidInterface *getHidInterface();\n\t\tLedGroup(HidInterface *hid);\n\n\tprivate:\n\t\tunsigned char indicator_;\n\t\tHidInterface *hid_;\n};\n\n#endif\n"
  },
  {
    "path": "src/core/virtual_input.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <cstdio>\n#include <iostream>\n\n#include <fcntl.h>\n#include <unistd.h>\n\n#include <linux/uinput.h>\n\n#include <sys/ioctl.h>\n\n#include \"virtual_input.hpp\"\n\n/**\n * Method for sending input events to the operating system.\n *\n * @param type type of input event, e.g. EV_KEY\n * @param code keycode defined in header file input.h\n * @param value value the event carries, e.g. EV_KEY: 0 represents release, 1\n * keypress and 2 autorepeat\n */\nvoid VirtualInput::sendEvent(short type, short code, int value) {\n\tstruct input_event inev = input_event();\n\tinev.type = type;\n\tinev.code = code;\n\tinev.value = value;\n\twrite(uifd_, &inev, sizeof(struct input_event));\n\tinev.type = EV_SYN;\n\tinev.code = 0;\n\tinev.value = 0;\n\twrite(uifd_, &inev, sizeof(struct input_event));\n}\n\n/**\n * Constructor setting up operating system specific back-ends.\n */\nVirtualInput::VirtualInput(struct Device *device, sidewinderd::DevNode *devNode, Process *process) {\n\tprocess_ = process;\n\tdevice_ = device;\n\tdevNode_ = devNode;\n\t/* for Linux */\n\tcreateUidev();\n}\n\nVirtualInput::~VirtualInput() {\n\tclose(uifd_);\n}\n\n/**\n * Creating a uinput virtual input device under Linux.\n */\nvoid VirtualInput::createUidev() {\n\t/* open uinput device with root privileges */\n\tprocess_->privilege();\n\tuifd_ = open(\"/dev/uinput\", O_WRONLY | O_NONBLOCK);\n\n\tif (uifd_ < 0) {\n\t\tuifd_ = open(\"/dev/input/uinput\", O_WRONLY | O_NONBLOCK);\n\n\t\tif (uifd_ < 0) {\n\t\t\tstd::cout << \"Can't open uinput\" << std::endl;\n\t\t}\n\t}\n\n\tprocess_->unprivilege();\n\t/* set all keybits */\n\tioctl(uifd_, UI_SET_EVBIT, EV_KEY);\n\n\tfor (int i = KEY_ESC; i <= KEY_KPDOT; i++) {\n\t\tioctl(uifd_, UI_SET_KEYBIT, i);\n\t}\n\n\tfor (int i = KEY_ZENKAKUHANKAKU; i <= KEY_F24; i++) {\n\t\tioctl(uifd_, UI_SET_KEYBIT, i);\n\t}\n\n\tfor (int i = KEY_PLAYCD; i <= KEY_MICMUTE; i++) {\n\t\tioctl(uifd_, UI_SET_KEYBIT, i);\n\t}\n\n\t/* uinput device details */\n\tstruct uinput_user_dev uidev = uinput_user_dev();\n\t/* TODO: copy device's name */\n\tsnprintf(uidev.name, UINPUT_MAX_NAME_SIZE, \"Sidewinderd\");\n\tuidev.id.bustype = BUS_USB;\n\tuidev.id.vendor = std::stoi(device_->vendor, nullptr, 16);\n\tuidev.id.product = std::stoi(device_->product, nullptr, 16);\n\tuidev.id.version = 1;\n\t/* write uinput device details */\n\twrite(uifd_, &uidev, sizeof(struct uinput_user_dev));\n\t/* create uinput device */\n\tioctl(uifd_, UI_DEV_CREATE);\n}\n"
  },
  {
    "path": "src/core/virtual_input.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef VIRTUALINPUT_CLASS_H\n#define VIRTUALINPUT_CLASS_H\n\n#include <process.hpp>\n#include <device_data.hpp>\n#include <core/device.hpp>\n\n/**\n * Class representing a virtual input device.\n *\n * Needed to send key events to the operating system. For Linux, uinput is used\n * as the back-end.\n */\nclass VirtualInput {\n\tpublic:\n\t\tvoid sendEvent(short type, short code, int value);\n\t\tVirtualInput(struct Device *device, sidewinderd::DevNode *devNode, Process *process);\n\t\t~VirtualInput();\n\n\tprivate:\n\t\tint uifd_; /**< uinput device file descriptor */\n\t\tProcess *process_; /**< process object for setting privileges */\n\t\tDevice *device_; /**< device information */\n\t\tsidewinderd::DevNode *devNode_; /**< device information */\n\t\tvoid createUidev();\n};\n\n#endif\n"
  },
  {
    "path": "src/device_data.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef DEVICEDATA_CLASS_H\n#define DEVICEDATA_CLASS_H\n\n#include <string>\n#include <vector>\n\nnamespace sidewinderd {\n\t/**\n\t * Struct for storing and passing paths to relevant /dev/<node> files.\n\t */\n\tstruct DevNode {\n\t\tstd::string hidraw, inputEvent; /**< path to hidraw and input event */\n\t};\n};\n\n#endif\n"
  },
  {
    "path": "src/main.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <iostream>\n#include <string>\n\n#include <getopt.h>\n\n#include <libconfig.h++>\n\n#include <process.hpp>\n#include <core/device_manager.hpp>\n\nvoid help(std::string name) {\n\tstd::cerr << \"Usage: \" << name << \" [options]\" << std::endl\n\t\t  << std::endl\n\t\t  << \"Options:\" << std::endl\n\t\t  << \"  -c, --config=<file>   Override default configuration file path\" << std::endl\n\t\t  << \"  -d, --daemon          Run process as daemon\" << std::endl\n\t\t  << \"  -h, --help            Print this screen\" << std::endl\n\t\t  << \"  -v, --version         Print program version\" << std::endl;\n}\n\nvoid setupConfig(libconfig::Config *config, std::string configFilePath = \"/etc/sidewinderd.conf\") {\n\ttry {\n\t\tconfig->readFile(configFilePath.c_str());\n\t} catch (const libconfig::FileIOException &fioex) {\n\t\tstd::cerr << \"I/O error while reading file.\" << std::endl;\n\t} catch (const libconfig::ParseException &pex) {\n\t\tstd::cerr << \"Parse error at \" << pex.getFile() << \":\" << pex.getLine() << \" - \" << pex.getError() << std::endl;\n\t}\n\n\tlibconfig::Setting &root = config->getRoot();\n\n\tif (!root.exists(\"user\")) {\n\t\troot.add(\"user\", libconfig::Setting::TypeString) = \"root\";\n\t}\n\n\tif (!root.exists(\"capture_delays\")) {\n\t\troot.add(\"capture_delays\", libconfig::Setting::TypeBoolean) = true;\n\t}\n\n\tif (!root.exists(\"pid-file\")) {\n\t\troot.add(\"pid-file\", libconfig::Setting::TypeString) = \"/var/run/sidewinderd.pid\";\n\t}\n\n\tif (!root.exists(\"encrypted_workdir\")) {\n\t\troot.add(\"encrypted_workdir\", libconfig::Setting::TypeBoolean) = false;\n\t}\n}\n\nint main(int argc, char *argv[]) {\n\t/* object for managing runtime information */\n\tProcess process;\n\n\t/* set program name */\n\tprocess.setName(argv[0]);\n\n\t/* handling command-line options */\n\tstatic struct option longOptions[] = {\n\t\t{\"config\", required_argument, 0, 'c'},\n\t\t{\"daemon\", no_argument, 0, 'd'},\n\t\t{\"help\", no_argument, 0, 'h'},\n\t\t{\"version\", no_argument, 0, 'v'},\n\t\t{0, 0, 0, 0}\n\t};\n\n\tint opt, index = 0;\n\tstd::string configFilePath;\n\n\t/* flags */\n\tbool shouldDaemonize = false;\n\n\twhile ((opt = getopt_long(argc, argv, \":c:dhv\", longOptions, &index)) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'c':\n\t\t\t\tconfigFilePath = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'd':\n\t\t\t\tshouldDaemonize = true;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\thelp(process.getName());\n\t\t\t\treturn EXIT_SUCCESS;\n\t\t\tcase 'v':\n\t\t\t\tstd::cerr << process.getName() << \" \" << process.getVersion() << std::endl;\n\t\t\t\treturn EXIT_SUCCESS;\n\t\t\tcase ':':\n\t\t\t\tstd::cerr << \"Missing argument.\" << std::endl;\n\t\t\t\treturn EXIT_FAILURE;\n\t\t\tcase '?':\n\t\t\t\tstd::cerr << \"Unknown option.\" << std::endl;\n\t\t\t\treturn EXIT_FAILURE;\n\t\t\tdefault:\n\t\t\t\tstd::cerr << \"Unexpected error.\" << std::endl;\n\t\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t}\n\n\t/* reading config file */\n\tlibconfig::Config config;\n\n\tif (configFilePath.empty()) {\n\t\tsetupConfig(&config);\n\t} else {\n\t\tsetupConfig(&config, configFilePath);\n\t}\n\n\t/* daemonize, if flag has been set */\n\tif (shouldDaemonize) {\n\t\tint ret = process.daemonize();\n\n\t\tif (ret > 0) {\n\t\t\treturn EXIT_SUCCESS;\n\t\t} else if (ret < 0) {\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t}\n\n\t/* creating pid file for single instance mechanism */\n\tif (process.createPid(config.lookup(\"pid-file\"))) {\n\t\treturn EXIT_FAILURE;\n\t}\n\n\t/* setting gid and uid to configured user */\n\tif (process.applyUser(config.lookup(\"user\"))) {\n\t\treturn EXIT_FAILURE;\n\t}\n\n\t// setting up working directory\n\tstd::string workdir;\n\n\tif (config.exists(\"workdir\")) {\n\t\tworkdir = config.lookup(\"workdir\").c_str();\n\t}\n\n\tif (process.createWorkdir(workdir, config.lookup(\"encrypted_workdir\"))) {\n\t\treturn EXIT_FAILURE;\n\t}\n\n\tstd::clog << \"Started sidewinderd.\" << std::endl;\n\tprocess.setActive(true);\n\n\tDeviceManager deviceManager(&config, &process);\n\n\tdeviceManager.monitor();\n\tprocess.destroyPid();\n\tstd::clog << \"Stopped sidewinderd.\" << std::endl;\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "src/process.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <chrono>\n#include <csignal>\n#include <iostream>\n#include <thread>\n\n#include <fcntl.h>\n#include <unistd.h>\n\n#include <sys/file.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n\n#include \"process.hpp\"\n\n/* constants */\nconstexpr auto version =\t\"0.4.4\";\nconstexpr auto wait =\t\t1;\n\nstd::atomic<bool> Process::isActive_;\n\nbool Process::isActive() {\n\treturn isActive_;\n}\n\nvoid Process::setActive(bool isActive) {\n\tisActive_ = isActive;\n}\n\nstd::string Process::getName() {\n\tif (name_.empty()) {\n\t\tname_ = \"sidewinderd\";\n\t}\n\n\treturn name_;\n}\n\nvoid Process::setName(std::string name) {\n\tname_ = name;\n}\n\nint Process::daemonize() {\n\tpid_t pid, sid;\n\tpid = fork();\n\n\tif (pid < 0) {\n\t\tstd::cerr << \"Error creating daemon.\" << std::endl;\n\t\treturn -1;\n\t}\n\n\tif (pid > 0) {\n\t\treturn 1;\n\t}\n\n\tsid = setsid();\n\n\tif (sid < 0) {\n\t\tstd::cerr << \"Error setting sid.\" << std::endl;\n\t\treturn -1;\n\t}\n\n\tpid = fork();\n\n\tif (pid < 0) {\n\t\tstd::cerr << \"Error forking second time.\" << std::endl;\n\t\treturn -1;\n\t}\n\n\tif (pid > 0) {\n\t\treturn 1;\n\t}\n\n\tumask(0);\n\tchdir(\"/\");\n\tclose(STDIN_FILENO);\n\tclose(STDOUT_FILENO);\n\tclose(STDERR_FILENO);\n\n\treturn 0;\n}\n\nint Process::createPid(std::string pidPath) {\n\tpidPath_ = pidPath;\n\tpidFd_ = open(pidPath_.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);\n\n\tif (pidFd_ < 0) {\n\t\tstd::cerr << \"PID file could not be created.\" << std::endl;\n\n\t\treturn -1;\n\t}\n\n\tif (flock(pidFd_, LOCK_EX | LOCK_NB) < 0) {\n\t\tstd::cerr << \"Could not lock PID file, another instance is already running.\" << std::endl;\n\t\tclose(pidFd_);\n\n\t\treturn -1;\n\t}\n\n\thasPid_ = true;\n\n\treturn 0;\n}\n\nvoid Process::destroyPid() {\n\tif (pidFd_) {\n\t\tflock(pidFd_, LOCK_UN);\n\t\tclose(pidFd_);\n\t}\n\n\tif (hasPid_) {\n\t\tunlink(pidPath_.c_str());\n\t\thasPid_ = false;\n\t}\n}\n\nint Process::applyUser(std::string user) {\n\tuser_ = user;\n\tpw_ = getpwnam(user_.c_str());\n\n\tif (pw_) {\n\t\tsetegid(pw_->pw_gid);\n\t\tseteuid(pw_->pw_uid);\n\t} else {\n\t\tstd::cerr << \"User not found.\" << std::endl;\n\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint Process::createWorkdir(std::string directory, bool isEncrypted) {\n\tif (user_.empty()) {\n\t\treturn 1;\n\t}\n\n\tstd::string workdir;\n\n\tif (directory.empty()) {\n\t\tstd::string xdgData;\n\t\tworkdir = pw_->pw_dir;\n\n\t\t// find user-specific data directory\n\t\tif (const char *env = std::getenv(\"XDG_DATA_HOME\")) {\n\t\t\txdgData = env;\n\n\t\t\tif (!xdgData.empty()) {\n\t\t\t\tworkdir = xdgData;\n\t\t\t}\n\t\t} else {\n\t\t\t// fallback to default\n\t\t\txdgData = \"/.local/share\";\n\t\t\tworkdir.append(xdgData);\n\t\t}\n\t} else {\n\t\tworkdir = directory;\n\t}\n\n\t// wait until encrypted drive becomes available\n\tif (isEncrypted) {\n\t\twhile (access(workdir.c_str(), F_OK)) {\n\t\t\tstd::this_thread::sleep_for(std::chrono::seconds(wait));\n\t\t}\n\t}\n\n\t// creating sidewinderd directory\n\tif (directory.empty()) {\n\t\tworkdir.append(\"/sidewinderd\");\n\t\tmkdir(workdir.c_str(), S_IRWXU);\n\t}\n\n\tif (chdir(workdir.c_str())) {\n\t\tstd::cerr << \"Error accessing \" << workdir << \".\" << std::endl;\n\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nvoid Process::privilege() {\n\tseteuid(0);\n}\n\nvoid Process::unprivilege() {\n\tif (user_.empty()) {\n\t\treturn;\n\t}\n\n\tseteuid(pw_->pw_uid);\n}\n\nstd::string Process::getVersion() {\n\treturn version;\n}\n\nvoid Process::sigHandler(int sig) {\n\tstd::cerr << std::endl << \"Stop signal received.\" << std::endl;\n\n\tswitch(sig) {\n\t\tcase SIGINT:\n\t\t\tsetActive(false);\n\t\t\tbreak;\n\t\tcase SIGTERM:\n\t\t\tsetActive(false);\n\t\t\tbreak;\n\t}\n}\n\nProcess::Process() {\n\tisActive_ = false;\n\thasPid_ = false;\n\tpidFd_ = 0;\n\n\t/* signal handling */\n\tstruct sigaction action;\n\taction.sa_handler = sigHandler;\n\tsigaction(SIGINT, &action, nullptr);\n\tsigaction(SIGTERM, &action, nullptr);\n}\n\nProcess::~Process() {\n\tdestroyPid();\n}\n"
  },
  {
    "path": "src/process.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef PROCESS_CLASS_H\n#define PROCESS_CLASS_H\n\n#include <atomic>\n#include <string>\n\n#include <pwd.h>\n\nclass Process {\n\tpublic:\n\t\tstatic bool isActive();\n\t\tstatic void setActive(bool isActive);\n\t\tstd::string getName();\n\t\tvoid setName(std::string name);\n\t\tint daemonize();\n\t\tint createPid(std::string pidPath);\n\t\tvoid destroyPid();\n\t\tint applyUser(std::string user);\n\t\tint createWorkdir(std::string directory, bool isEncrypted);\n\t\tvoid privilege();\n\t\tvoid unprivilege();\n\t\tstd::string getVersion();\n\t\tProcess();\n\t\t~Process();\n\n\tprivate:\n\t\tstatic std::atomic<bool> isActive_;\n\t\tbool hasPid_;\n\t\tint pidFd_;\n\t\tstd::string name_;\n\t\tstd::string user_;\n\t\tstd::string pidPath_;\n\t\tstruct passwd *pw_;\n\t\tstatic void sigHandler(int sig);\n};\n\n#endif\n"
  },
  {
    "path": "src/vendor/logitech/g103.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <cstdio>\n#include <ctime>\n#include <iostream>\n#include <thread>\n\n#include <fcntl.h>\n#include <tinyxml2.h>\n#include <unistd.h>\n\n#include <linux/hidraw.h>\n#include <linux/input.h>\n\n#include <sys/ioctl.h>\n#include <sys/stat.h>\n\n#include \"g103.hpp\"\n\n/* constants */\nconstexpr auto G103_FEATURE_REPORT_MACRO =\t0x08;\nconstexpr auto G103_FEATURE_REPORT_MACRO_SIZE =\t7;\n\nvoid LogitechG103::setProfile(int profile) {\n\tprofile_ = profile;\n}\n\n/*\n * get_input() checks, which keys were pressed. The macro keys are packed in a\n * 3-byte buffer.\n */\n/*\n * TODO: only return latest pressed key, if multiple keys have been pressed at\n * the same time.\n */\nstruct KeyData LogitechG103::getInput() {\n\tstruct KeyData keyData = KeyData();\n\tint key, nBytes;\n\tunsigned char buf[MAX_BUF];\n\tnBytes = read(fd_, buf, MAX_BUF);\n\n\tif (nBytes == 3 && buf[0] == 0x03) {\n\t\t/*\n\t\t * cutting off buf[0], which is used to differentiate between macro and\n\t\t * media keys. Our task is now to translate the buffer codes to\n\t\t * something we can work with. Here is a table, where you can look up\n\t\t * the keys and buffer, if you want to improve the current method:\n\t\t *\n\t\t * G1\t0x03 0x01 0x00 - buf[1]\n\t\t * G2\t0x03 0x02 0x00 - buf[1]\n\t\t * G3\t0x03 0x04 0x00 - buf[1]\n\t\t * G4\t0x03 0x08 0x00 - buf[1]\n\t\t * G5\t0x03 0x10 0x00 - buf[1]\n\t\t * G6\t0x03 0x20 0x00 - buf[1]\n\t\t */\n\t\tif (buf[2] == 0) {\n\t\t\tkey = (static_cast<int>(buf[1]));\n\t\t\tkey = ffs(key);\n\n\t\t\tif (key) {\n\t\t\t\tkeyData.index = key;\n\t\t\t\tkeyData.type = KeyData::KeyType::Macro;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn keyData;\n}\n\nvoid LogitechG103::handleKey(struct KeyData *keyData) {\n\tif (keyData->index != 0) {\n\t\tif (keyData->type == KeyData::KeyType::Macro) {\n\t\t\tKey key(keyData);\n\t\t\tstd::string macroPath = key.getMacroPath(profile_);\n\t\t\tstd::thread thread(playMacro, macroPath, virtInput_);\n\t\t\tthread.detach();\n\t\t}\n\t}\n}\n\nvoid LogitechG103::resetMacroKeys() {\n\t/* we need to zero out the report, so macro keys don't emit F-keys */\n\tunsigned char buf[G103_FEATURE_REPORT_MACRO_SIZE] = {};\n\t/* buf[0] is Report ID */\n\tbuf[0] = G103_FEATURE_REPORT_MACRO;\n\tioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);\n}\n\nLogitechG103::LogitechG103(struct Device *device,\n\t\tsidewinderd::DevNode *devNode, libconfig::Config *config,\n\t\tProcess *process) :\n\t\tKeyboard::Keyboard(device, devNode, config, process) {\n\tresetMacroKeys();\n\n\t// set profile to default, as no profile switching is supported\n\tsetProfile(0);\n}\n\nLogitechG103::~LogitechG103() {\n\tstd::cerr << \"LogitechG103 Destructor\" << std::endl;\n\n\t// keyboard is not connected anymore, join the thread\n\tif (listenThread_.joinable()) {\n\t\tlistenThread_.join();\n\t}\n}\n"
  },
  {
    "path": "src/vendor/logitech/g103.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef LOGITECH_G103_CLASS_H\n#define LOGITECH_G103_CLASS_H\n\n#include <core/keyboard.hpp>\n\nclass LogitechG103 : public Keyboard {\n\tpublic:\n\t\tLogitechG103(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);\n\t\t~LogitechG103();\n\n\tprotected:\n\t\tstruct KeyData getInput();\n\t\tvoid handleKey(struct KeyData *keyData);\n\n\tprivate:\n\t\tvoid setProfile(int profile);\n\t\tvoid resetMacroKeys();\n};\n\n#endif\n"
  },
  {
    "path": "src/vendor/logitech/g105.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <cstdio>\n#include <ctime>\n#include <iostream>\n#include <thread>\n\n#include <fcntl.h>\n#include <tinyxml2.h>\n#include <unistd.h>\n\n#include <linux/hidraw.h>\n#include <linux/input.h>\n\n#include <sys/ioctl.h>\n#include <sys/stat.h>\n\n#include \"g105.hpp\"\n\n/* constants */\nconstexpr auto G105_FEATURE_REPORT_LED =\t0x06;\nconstexpr auto G105_FEATURE_REPORT_MACRO =\t0x08;\nconstexpr auto G105_FEATURE_REPORT_MACRO_SIZE =\t7;\nconstexpr auto G105_LED_M1 =\t\t\t0x01;\nconstexpr auto G105_LED_M2 =\t\t\t0x02;\nconstexpr auto G105_LED_M3 =\t\t\t0x04;\nconstexpr auto G105_LED_MR =\t\t\t0x08;\nconstexpr auto G105_KEY_M1 =\t\t\t0x01;\nconstexpr auto G105_KEY_M2 =\t\t\t0x02;\nconstexpr auto G105_KEY_M3 =\t\t\t0x03;\nconstexpr auto G105_KEY_MR =\t\t\t0x04;\n\nvoid LogitechG105::setProfile(int profile) {\n\tprofile_ = profile;\n\n\tswitch (profile_) {\n\t\tcase 0: ledProfile1_.on(); break;\n\t\tcase 1: ledProfile2_.on(); break;\n\t\tcase 2: ledProfile3_.on(); break;\n\t}\n}\n\n/*\n * get_input() checks, which keys were pressed. The macro keys are packed in a\n * 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.\n */\n/*\n * TODO: only return latest pressed key, if multiple keys have been pressed at\n * the same time.\n */\nstruct KeyData LogitechG105::getInput() {\n\tstruct KeyData keyData = KeyData();\n\tint key, nBytes;\n\tunsigned char buf[MAX_BUF];\n\tnBytes = read(fd_, buf, MAX_BUF);\n\n\tif (nBytes == 3 && buf[0] == 0x03) {\n\t\t/*\n\t\t * cutting off buf[0], which is used to differentiate between macro and\n\t\t * media keys. Our task is now to translate the buffer codes to\n\t\t * something we can work with. Here is a table, where you can look up\n\t\t * the keys and buffer, if you want to improve the current method:\n\t\t *\n\t\t * G1\t0x03 0x01 0x00 - buf[1]\n\t\t * G2\t0x03 0x02 0x00 - buf[1]\n\t\t * G3\t0x03 0x04 0x00 - buf[1]\n\t\t * G4\t0x03 0x08 0x00 - buf[1]\n\t\t * G5\t0x03 0x10 0x00 - buf[1]\n\t\t * G6\t0x03 0x20 0x00 - buf[1]\n\t\t * M1\t0x03 0x00 0x01 - buf[2]\n\t\t * M2\t0x03 0x00 0x02 - buf[2]\n\t\t * M3\t0x03 0x00 0x04 - buf[2]\n\t\t * MR\t0x03 0x00 0x08 - buf[2]\n\t\t */\n\t\tif (buf[2] == 0) {\n\t\t\tkey = (static_cast<int>(buf[1]));\n\t\t\tkey = ffs(key);\n\n\t\t\tif (key) {\n\t\t\t\tkeyData.index = key;\n\t\t\t\tkeyData.type = KeyData::KeyType::Macro;\n\t\t\t}\n\t\t} else if (buf[1] == 0) {\n\t\t\tkey = (static_cast<int>(buf[2]));\n\t\t\tkey = ffs(key);\n\n\t\t\tif (key) {\n\t\t\t\tkeyData.index = key;\n\t\t\t\tkeyData.type = KeyData::KeyType::Extra;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn keyData;\n}\n\nvoid LogitechG105::handleKey(struct KeyData *keyData) {\n\tif (keyData->index != 0) {\n\t\tif (keyData->type == KeyData::KeyType::Macro) {\n\t\t\tKey key(keyData);\n\t\t\tstd::string macroPath = key.getMacroPath(profile_);\n\t\t\tstd::thread thread(playMacro, macroPath, virtInput_);\n\t\t\tthread.detach();\n\t\t} else if (keyData->type == KeyData::KeyType::Extra) {\n\t\t\tif (keyData->index == G105_KEY_M1) {\n\t\t\t\t/* M1 key */\n\t\t\t\tsetProfile(0);\n\t\t\t} else if (keyData->index == G105_KEY_M2) {\n\t\t\t\t/* M2 key */\n\t\t\t\tsetProfile(1);\n\t\t\t} else if (keyData->index == G105_KEY_M3) {\n\t\t\t\t/* M3 key */\n\t\t\t\tsetProfile(2);\n\t\t\t} else if (keyData->index == G105_KEY_MR) {\n\t\t\t\t/* MR key */\n\t\t\t\thandleRecordMode(&ledRecord_, G105_KEY_MR);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid LogitechG105::resetMacroKeys() {\n\t/* we need to zero out the report, so macro keys don't emit numbers */\n\tunsigned char buf[G105_FEATURE_REPORT_MACRO_SIZE] = {};\n\t/* buf[0] is Report ID */\n\tbuf[0] = G105_FEATURE_REPORT_MACRO;\n\tioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);\n}\n\nLogitechG105::LogitechG105(struct Device *device,\n\t\tsidewinderd::DevNode *devNode, libconfig::Config *config,\n\t\tProcess *process) :\n\t\tKeyboard::Keyboard(device, devNode, config, process),\n\t\tgroup_{&hid_},\n\t\tledProfile1_{G105_FEATURE_REPORT_LED, G105_LED_M1, &group_},\n\t\tledProfile2_{G105_FEATURE_REPORT_LED, G105_LED_M2, &group_},\n\t\tledProfile3_{G105_FEATURE_REPORT_LED, G105_LED_M3, &group_},\n\t\tledRecord_{G105_FEATURE_REPORT_LED, G105_LED_MR, &group_} {\n\tledProfile1_.setLedType(LedType::Profile);\n\tledProfile2_.setLedType(LedType::Profile);\n\tledProfile3_.setLedType(LedType::Profile);\n\tledRecord_.setLedType(LedType::Indicator);\n\tresetMacroKeys();\n\n\t// set initial LED\n\tledProfile1_.on();\n}\n\nLogitechG105::~LogitechG105() {\n\tstd::cerr << \"LogitechG105 Destructor\" << std::endl;\n\n\t// keyboard is not connected anymore, join the thread\n\tif (listenThread_.joinable()) {\n\t\tlistenThread_.join();\n\t}\n}\n"
  },
  {
    "path": "src/vendor/logitech/g105.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef LOGITECH_G105_CLASS_H\n#define LOGITECH_G105_CLASS_H\n\n#include <core/keyboard.hpp>\n#include <core/led_group.hpp>\n\nclass LogitechG105 : public Keyboard {\n\tpublic:\n\t\tLogitechG105(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);\n\t\t~LogitechG105();\n\n\tprotected:\n\t\tstruct KeyData getInput();\n\t\tvoid handleKey(struct KeyData *keyData);\n\n\tprivate:\n\t\tLedGroup group_;\n\t\tLed ledProfile1_;\n\t\tLed ledProfile2_;\n\t\tLed ledProfile3_;\n\t\tLed ledRecord_;\n\t\tvoid setProfile(int profile);\n\t\tvoid resetMacroKeys();\n};\n\n#endif\n"
  },
  {
    "path": "src/vendor/logitech/g710.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <cstdio>\n#include <ctime>\n#include <iostream>\n#include <thread>\n\n#include <fcntl.h>\n#include <tinyxml2.h>\n#include <unistd.h>\n\n#include <linux/hidraw.h>\n#include <linux/input.h>\n\n#include <sys/ioctl.h>\n#include <sys/stat.h>\n\n#include \"g710.hpp\"\n\n/* constants */\nconstexpr auto G710_FEATURE_REPORT_LED =\t0x06;\nconstexpr auto G710_FEATURE_REPORT_MACRO =\t0x09;\nconstexpr auto G710_FEATURE_REPORT_MACRO_SIZE =\t13;\nconstexpr auto G710_LED_M1 =\t\t\t0x10;\nconstexpr auto G710_LED_M2 =\t\t\t0x20;\nconstexpr auto G710_LED_M3 =\t\t\t0x40;\nconstexpr auto G710_LED_MR =\t\t\t0x80;\nconstexpr auto G710_KEY_M1 =\t\t\t0x01;\nconstexpr auto G710_KEY_M2 =\t\t\t0x02;\nconstexpr auto G710_KEY_M3 =\t\t\t0x03;\nconstexpr auto G710_KEY_MR =\t\t\t0x04;\n\nvoid LogitechG710::setProfile(int profile) {\n\tprofile_ = profile;\n\n\tswitch (profile_) {\n\t\tcase 0: ledProfile1_.on(); break;\n\t\tcase 1: ledProfile2_.on(); break;\n\t\tcase 2: ledProfile3_.on(); break;\n\t}\n}\n\n/*\n * get_input() checks, which keys were pressed. The macro keys are packed in a\n * 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.\n */\n/*\n * TODO: only return latest pressed key, if multiple keys have been pressed at\n * the same time.\n */\nstruct KeyData LogitechG710::getInput() {\n\tstruct KeyData keyData = KeyData();\n\tint key, nBytes;\n\tunsigned char buf[MAX_BUF];\n\tnBytes = read(fd_, buf, MAX_BUF);\n\n\tif (nBytes == 4 && buf[0] == 0x03) {\n\t\t/*\n\t\t * cutting off buf[0], which is used to differentiate between macro and\n\t\t * media keys. Our task is now to translate the buffer codes to\n\t\t * something we can work with. Here is a table, where you can look up\n\t\t * the keys and buffer, if you want to improve the current method:\n\t\t *\n\t\t * G1\t0x03 0x01 0x00 0x00 - buf[1]\n\t\t * G2\t0x03 0x02 0x00 0x00 - buf[1]\n\t\t * G3\t0x03 0x04 0x00 0x00 - buf[1]\n\t\t * G4\t0x03 0x08 0x00 0x00 - buf[1]\n\t\t * G5\t0x03 0x10 0x00 0x00 - buf[1]\n\t\t * G6\t0x03 0x20 0x00 0x00 - buf[1]\n\t\t * M1\t0x03 0x00 0x10 0x00 - buf[2]\n\t\t * M2\t0x03 0x00 0x20 0x00 - buf[2]\n\t\t * M3\t0x03 0x00 0x40 0x00 - buf[2]\n\t\t * MR\t0x03 0x00 0x80 0x00 - buf[2]\n\t\t */\n\t\tif (buf[2] == 0) {\n\t\t\tkey = (static_cast<int>(buf[1]));\n\t\t\tkey = ffs(key);\n\n\t\t\tif (key) {\n\t\t\t\tkeyData.index = key;\n\t\t\t\tkeyData.type = KeyData::KeyType::Macro;\n\t\t\t}\n\t\t} else if (buf[1] == 0) {\n\t\t\tkey = (static_cast<int>(buf[2])) >> 4;\n\t\t\tkey = ffs(key);\n\n\t\t\tif (key) {\n\t\t\t\tkeyData.index = key;\n\t\t\t\tkeyData.type = KeyData::KeyType::Extra;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn keyData;\n}\n\nvoid LogitechG710::handleKey(struct KeyData *keyData) {\n\tif (keyData->index != 0) {\n\t\tif (keyData->type == KeyData::KeyType::Macro) {\n\t\t\tKey key(keyData);\n\t\t\tstd::string macroPath = key.getMacroPath(profile_);\n\t\t\tstd::thread thread(playMacro, macroPath, virtInput_);\n\t\t\tthread.detach();\n\t\t} else if (keyData->type == KeyData::KeyType::Extra) {\n\t\t\tif (keyData->index == G710_KEY_M1) {\n\t\t\t\t/* M1 key */\n\t\t\t\tsetProfile(0);\n\t\t\t} else if (keyData->index == G710_KEY_M2) {\n\t\t\t\t/* M2 key */\n\t\t\t\tsetProfile(1);\n\t\t\t} else if (keyData->index == G710_KEY_M3) {\n\t\t\t\t/* M3 key */\n\t\t\t\tsetProfile(2);\n\t\t\t} else if (keyData->index == G710_KEY_MR) {\n\t\t\t\t/* MR key */\n\t\t\t\thandleRecordMode(&ledRecord_, G710_KEY_MR);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid LogitechG710::resetMacroKeys() {\n\t/* we need to zero out the report, so macro keys don't emit numbers */\n\tunsigned char buf[G710_FEATURE_REPORT_MACRO_SIZE] = {};\n\t/* buf[0] is Report ID */\n\tbuf[0] = G710_FEATURE_REPORT_MACRO;\n\tioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);\n}\n\nLogitechG710::LogitechG710(struct Device *device,\n\t\tsidewinderd::DevNode *devNode, libconfig::Config *config,\n\t\tProcess *process) :\n\t\tKeyboard::Keyboard(device, devNode, config, process),\n\t\tgroup_{&hid_},\n\t\tledProfile1_{G710_FEATURE_REPORT_LED, G710_LED_M1, &group_},\n\t\tledProfile2_{G710_FEATURE_REPORT_LED, G710_LED_M2, &group_},\n\t\tledProfile3_{G710_FEATURE_REPORT_LED, G710_LED_M3, &group_},\n\t\tledRecord_{G710_FEATURE_REPORT_LED, G710_LED_MR, &group_} {\n\tledProfile1_.setLedType(LedType::Profile);\n\tledProfile2_.setLedType(LedType::Profile);\n\tledProfile3_.setLedType(LedType::Profile);\n\tledRecord_.setLedType(LedType::Indicator);\n\tresetMacroKeys();\n\n\t// set initial LED\n\tledProfile1_.on();\n}\n\nLogitechG710::~LogitechG710() {\n\tstd::cerr << \"LogitechG710 Destructor\" << std::endl;\n\n\t// keyboard is not connected anymore, join the thread\n\tif (listenThread_.joinable()) {\n\t\tlistenThread_.join();\n\t}\n}\n"
  },
  {
    "path": "src/vendor/logitech/g710.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef LOGITECH_G710_PLUS_CLASS_H\n#define LOGITECH_G710_PLUS_CLASS_H\n\n#include <core/keyboard.hpp>\n#include <core/led_group.hpp>\n\nclass LogitechG710 : public Keyboard {\n\tpublic:\n\t\tLogitechG710(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);\n\t\t~LogitechG710();\n\n\tprotected:\n\t\tstruct KeyData getInput();\n\t\tvoid handleKey(struct KeyData *keyData);\n\n\tprivate:\n\t\tLedGroup group_;\n\t\tLed ledProfile1_;\n\t\tLed ledProfile2_;\n\t\tLed ledProfile3_;\n\t\tLed ledRecord_;\n\t\tvoid setProfile(int profile);\n\t\tvoid resetMacroKeys();\n};\n\n#endif\n"
  },
  {
    "path": "src/vendor/microsoft/sidewinder.cpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#include <cstdio>\n#include <ctime>\n#include <iostream>\n#include <thread>\n\n#include <fcntl.h>\n#include <tinyxml2.h>\n#include <unistd.h>\n\n#include <linux/hidraw.h>\n#include <linux/input.h>\n\n#include <sys/ioctl.h>\n#include <sys/stat.h>\n\n#include \"sidewinder.hpp\"\n\n/* constants */\nconstexpr auto SW_FEATURE_REPORT =\t0x07;\nconstexpr auto SW_MACRO_PAD =\t\t0x01;\nconstexpr auto SW_LED_AUTO =\t\t0x02;\nconstexpr auto SW_LED_P1 =\t\t0x04;\nconstexpr auto SW_LED_P2 =\t\t0x08;\nconstexpr auto SW_LED_P3 =\t\t0x10;\nconstexpr auto SW_LED_RECORD =\t\t0x60;\nconstexpr auto SW_LED_RECORD_BLINK =\t0x40;\nconstexpr auto SW_KEY_GAMECENTER =\t0x10;\nconstexpr auto SW_KEY_RECORD =\t\t0x11;\nconstexpr auto SW_KEY_PROFILE =\t\t0x14;\n\nvoid SideWinder::toggleMacroPad() {\n\tauto report = hid_.getReport(SW_FEATURE_REPORT);\n\treport ^= SW_MACRO_PAD;\n\tmacroPad_ = report & SW_MACRO_PAD;\n\thid_.setReport(SW_FEATURE_REPORT, report);\n}\n\nvoid SideWinder::switchProfile() {\n\tprofile_ = (profile_ + 1) % MAX_PROFILE;\n\n\tswitch (profile_) {\n\t\tcase 0: ledProfile1_.on(); break;\n\t\tcase 1: ledProfile2_.on(); break;\n\t\tcase 2: ledProfile3_.on(); break;\n\t}\n}\n\n/*\n * get_input() checks, which keys were pressed. The macro keys are packed in a\n * 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.\n */\n/*\n * TODO: only return latest pressed key, if multiple keys have been pressed at\n * the same time.\n */\nstruct KeyData SideWinder::getInput() {\n\tstruct KeyData keyData = KeyData();\n\tint key, nBytes;\n\tunsigned char buf[MAX_BUF];\n\tnBytes = read(fd_, buf, MAX_BUF);\n\n\tif (nBytes == 5 && buf[0] == 8) {\n\t\t/*\n\t\t * cutting off buf[0], which is used to differentiate between macro and\n\t\t * media keys. Our task is now to translate the buffer codes to\n\t\t * something we can work with. Here is a table, where you can look up\n\t\t * the keys and buffer, if you want to improve the current method:\n\t\t *\n\t\t * S1\t0x08 0x01 0x00 0x00 0x00 - buf[1]\n\t\t * S2\t0x08 0x02 0x00 0x00 0x00 - buf[1]\n\t\t * S3\t0x08 0x04 0x00 0x00 0x00 - buf[1]\n\t\t * S4\t0x08 0x08 0x00 0x00 0x00 - buf[1]\n\t\t * S5\t0x08 0x10 0x00 0x00 0x00 - buf[1]\n\t\t * S6\t0x08 0x20 0x00 0x00 0x00 - buf[1]\n\t\t * S7\t0x08 0x40 0x00 0x00 0x00 - buf[1]\n\t\t * S8\t0x08 0x80 0x00 0x00 0x00 - buf[1]\n\t\t * S9\t0x08 0x00 0x01 0x00 0x00 - buf[2]\n\t\t * S10\t0x08 0x00 0x02 0x00 0x00 - buf[2]\n\t\t * S11\t0x08 0x00 0x04 0x00 0x00 - buf[2]\n\t\t * S12\t0x08 0x00 0x08 0x00 0x00 - buf[2]\n\t\t * S13\t0x08 0x00 0x10 0x00 0x00 - buf[2]\n\t\t * S14\t0x08 0x00 0x20 0x00 0x00 - buf[2]\n\t\t * S15\t0x08 0x00 0x40 0x00 0x00 - buf[2]\n\t\t * S16\t0x08 0x00 0x80 0x00 0x00 - buf[2]\n\t\t * S17\t0x08 0x00 0x00 0x01 0x00 - buf[3]\n\t\t * S18\t0x08 0x00 0x00 0x02 0x00 - buf[3]\n\t\t * S19\t0x08 0x00 0x00 0x04 0x00 - buf[3]\n\t\t * S20\t0x08 0x00 0x00 0x08 0x00 - buf[3]\n\t\t * S21\t0x08 0x00 0x00 0x10 0x00 - buf[3]\n\t\t * S22\t0x08 0x00 0x00 0x20 0x00 - buf[3]\n\t\t * S23\t0x08 0x00 0x00 0x40 0x00 - buf[3]\n\t\t * S24\t0x08 0x00 0x00 0x80 0x00 - buf[3]\n\t\t * S25\t0x08 0x00 0x00 0x00 0x01 - buf[4]\n\t\t * S26\t0x08 0x00 0x00 0x00 0x02 - buf[4]\n\t\t * S27\t0x08 0x00 0x00 0x00 0x04 - buf[4]\n\t\t * S28\t0x08 0x00 0x00 0x00 0x08 - buf[4]\n\t\t * S29\t0x08 0x00 0x00 0x00 0x10 - buf[4]\n\t\t * S30\t0x08 0x00 0x00 0x00 0x20 - buf[4]\n\t\t */\n\t\tkey = (static_cast<int>(buf[1]))\n\t\t\t| (static_cast<int>(buf[2]) << 8)\n\t\t\t| (static_cast<int>(buf[3]) << 16)\n\t\t\t| (static_cast<int>(buf[4]) << 24);\n\t\tkey = ffs(key);\n\n\t\tif (key) {\n\t\t\tkeyData.index = key;\n\t\t\tkeyData.type = KeyData::KeyType::Macro;\n\t\t}\n\t} else if (nBytes == 8 && buf[0] == 1 && buf[6]) {\n\t\t/* buf[0] == 1 means media keys, buf[6] shows pressed key */\n\t\tkeyData.index = buf[6];\n\t\tkeyData.type = KeyData::KeyType::Extra;\n\t}\n\n\treturn keyData;\n}\n\nvoid SideWinder::handleKey(struct KeyData *keyData) {\n\tif (keyData->type == KeyData::KeyType::Macro) {\n\t\tKey key(keyData);\n\t\tstd::string macroPath = key.getMacroPath(profile_);\n\t\tstd::thread thread(playMacro, macroPath, virtInput_);\n\t\tthread.detach();\n\t} else if (keyData->type == KeyData::KeyType::Extra) {\n\t\tif (keyData->index == SW_KEY_GAMECENTER) {\n\t\t\ttoggleMacroPad();\n\t\t} else if (keyData->index == SW_KEY_RECORD) {\n\t\t\thandleRecordMode(&ledRecord_, SW_KEY_RECORD);\n\t\t} else if (keyData->index == SW_KEY_PROFILE) {\n\t\t\tswitchProfile();\n\t\t}\n\t}\n}\n\nSideWinder::SideWinder(struct Device *device,\n\t\tsidewinderd::DevNode *devNode, libconfig::Config *config,\n\t\tProcess *process) :\n\t\tKeyboard::Keyboard(device, devNode, config, process),\n\t\tgroup_{&hid_},\n\t\tledProfile1_{SW_FEATURE_REPORT, SW_LED_P1, &group_},\n\t\tledProfile2_{SW_FEATURE_REPORT, SW_LED_P2, &group_},\n\t\tledProfile3_{SW_FEATURE_REPORT, SW_LED_P3, &group_},\n\t\tledRecord_{SW_FEATURE_REPORT, SW_LED_RECORD, &group_},\n\t\tledAuto_{SW_FEATURE_REPORT, SW_LED_AUTO, &group_} {\n\tledProfile1_.setLedType(LedType::Profile);\n\tledProfile2_.setLedType(LedType::Profile);\n\tledProfile3_.setLedType(LedType::Profile);\n\tledRecord_.setLedType(LedType::Indicator);\n\tledRecord_.registerBlink(SW_LED_RECORD_BLINK);\n\tledAuto_.setLedType(LedType::Indicator);\n\n\t// needed to avoid resetting macropad mode after bank switch\n\t// TODO: use a better solution after Led handling has been rewritten\n\tauto indicator= group_.getIndicatorMask();\n\tindicator |= SW_MACRO_PAD;\n\tgroup_.setIndicatorMask(indicator);\n\n\t// set initial LED\n\tledProfile1_.on();\n}\n\nSideWinder::~SideWinder() {\n\tstd::cerr << \"SideWinder Destructor\" << std::endl;\n\n\t// keyboard is not connected anymore, join the thread\n\tif (listenThread_.joinable()) {\n\t\tlistenThread_.join();\n\t}\n}\n"
  },
  {
    "path": "src/vendor/microsoft/sidewinder.hpp",
    "content": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and is distributed under the\n * MIT License. For more information, see LICENSE file.\n */\n\n#ifndef MICROSOFT_SIDEWINDER_CLASS_H\n#define MICROSOFT_SIDEWINDER_CLASS_H\n\n#include <core/keyboard.hpp>\n#include <core/led_group.hpp>\n\nclass SideWinder : public Keyboard {\n\tpublic:\n\t\tSideWinder(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);\n\t\t~SideWinder();\n\n\tprotected:\n\t\tstruct KeyData getInput();\n\t\tvoid handleKey(struct KeyData *keyData);\n\n\tprivate:\n\t\tLedGroup group_;\n\t\tLed ledProfile1_;\n\t\tLed ledProfile2_;\n\t\tLed ledProfile3_;\n\t\tLed ledRecord_;\n\t\tLed ledAuto_;\n\t\tunsigned char macroPad_;\n\t\tvoid toggleMacroPad();\n\t\tvoid switchProfile();\n};\n\n#endif\n"
  }
]