Full Code of tolga9009/sidewinderd for AI

master 638bc839d74d cached
38 files
65.7 KB
20.5k tokens
93 symbols
1 requests
Download .txt
Repository: tolga9009/sidewinderd
Branch: master
Commit: 638bc839d74d
Files: 38
Total size: 65.7 KB

Directory structure:
gitextract_np3q0_ka/

├── .gitignore
├── CMakeLists.txt
├── CONTRIBUTING
├── LICENSE
├── README.md
├── cmake/
│   ├── FindConfig++.cmake
│   ├── FindTinyXML2.cmake
│   └── FindUDev.cmake
├── etc/
│   ├── sidewinderd.conf
│   └── sidewinderd.service.in
└── src/
    ├── CMakeLists.txt
    ├── core/
    │   ├── device.hpp
    │   ├── device_manager.cpp
    │   ├── device_manager.hpp
    │   ├── hid_interface.cpp
    │   ├── hid_interface.hpp
    │   ├── key.cpp
    │   ├── key.hpp
    │   ├── keyboard.cpp
    │   ├── keyboard.hpp
    │   ├── led.cpp
    │   ├── led.hpp
    │   ├── led_group.cpp
    │   ├── led_group.hpp
    │   ├── virtual_input.cpp
    │   └── virtual_input.hpp
    ├── device_data.hpp
    ├── main.cpp
    ├── process.cpp
    ├── process.hpp
    └── vendor/
        ├── logitech/
        │   ├── g103.cpp
        │   ├── g103.hpp
        │   ├── g105.cpp
        │   ├── g105.hpp
        │   ├── g710.cpp
        │   └── g710.hpp
        └── microsoft/
            ├── sidewinder.cpp
            └── sidewinder.hpp

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
build
CMakeLists.txt.user


================================================
FILE: CMakeLists.txt
================================================
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8)
PROJECT(sidewinderd)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra")

ADD_SUBDIRECTORY(src)

SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")

FIND_PACKAGE(Config++ REQUIRED)
FIND_PACKAGE(TinyXML2 REQUIRED)
FIND_PACKAGE(UDev REQUIRED)


================================================
FILE: CONTRIBUTING
================================================
Developer Certificate of Origin
Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.


Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.


================================================
FILE: LICENSE
================================================
Sidewinder daemon is made available under the MIT License.

Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Sidewinder daemon

This project provides support for gaming peripherals under Linux. It was
originally designed for the Microsoft SideWinder X4, but we have extended
support for more keyboards. Our goal is to create a framework-like environment
for rapid driver development under Linux.


## Supported devices

  * Microsoft SideWinder X4
  * Microsoft SideWinder X6
  * Logitech G103
  * Logitech G105
  * Logitech G710
  * Logitech G710+


## Install

Please check, if this project has already been packaged for your specific Linux
distribution. Currently maintained packages:

  * Arch Linux: https://aur.archlinux.org/packages/sidewinderd/

If there are no packages available for your Linux distribution, please proceed
with the manual installation from source:

1. Install the following dependencies. Please refer to your specific Linux
distribution, as package names might differ.

  * cmake 2.8.8 (make)
  * libconfig 1.4.9
  * tinyxml2 2.2.0
  * libudev 210

2. Create a build directory in the toplevel directory:

    ```
    mkdir build
    ```

3. Run cmake from within the new build directory:

    ```
    cd build
    cmake ..
    ```

4. Compile and install:

    ```
    make
    make install
    ```


## Usage

Enable and start Sidewinder daemon:

    systemctl enable sidewinderd.service
    systemctl start sidewinderd.service

Configure `/etc/sidewinderd.conf` according to your needs. Please change the
user, as the default user is root.

You can now use your gaming peripheral! Please note, that there is no graphical
user interface. Some LEDs might light up, letting you know, that Sidewinder
daemon has successfully recognized your keyboard.


## Record macros

The macro keys are fully programmable, but there is no default event. You can
add functions to them, by recording macros or key combinations:

1. Choose a profile. The profile LED on the device will show you, which profile
is active.
2. Press record key. The record LED will light up.
3. Now, choose and press a macro key. On some devices, the record LED will begin
to blink. You're now in macro mode. Please note, that existing macros may get
overwritten.
4. Everything you type on the keyboard will get recorded. You can either record
a single key combination or a long series of keypresses. Please note, that
keypresses still send events to your operating system and your programs.
5. When done, press the record key again. This will stop recording and save the
macro.
6. You've now created a macro. Use it by setting the chosen profile and pressing
the chosen macro key.


## Contribution

In order to contribute to this project, you need to read and agree the Developer
Certificate of Origin, which can be found in the CONTRIBUTING file. Therefore,
commits need to be signed-off. You can do this by adding the `-s` flag when
commiting: `git commit -s`. Pseudonyms and anonymous contributions will not be
accepted.


## License

This project is made available under the MIT License. For more information,
please refer to the LICENSE file.


================================================
FILE: cmake/FindConfig++.cmake
================================================
FIND_PATH(CONFIG++_INCLUDE_DIR libconfig.h++ /usr/include /usr/local/include)

FIND_LIBRARY(CONFIG++_LIBRARY NAMES config++ PATH /usr/lib /usr/local/lib)

IF(CONFIG++_INCLUDE_DIR AND CONFIG++_LIBRARY)
	SET(CONFIG++_FOUND TRUE)
ENDIF(CONFIG++_INCLUDE_DIR AND CONFIG++_LIBRARY)

IF(CONFIG++_FOUND)
	IF(NOT CONFIG++_FIND_QUIETLY)
		MESSAGE(STATUS "Found Config++: ${CONFIG++_LIBRARY}")
	ENDIF(NOT CONFIG++_FIND_QUIETLY)
ELSE(CONFIG++_FOUND)
	IF(CONFIG++_FIND_REQUIRED)
		IF(NOT CONFIG++_INCLUDE_DIR)
			MESSAGE(FATAL_ERROR "Could not find LibConfig++ header file!")
		ENDIF(NOT CONFIG++_INCLUDE_DIR)

		IF(NOT CONFIG++_LIBRARY)
			MESSAGE(FATAL_ERROR "Could not find LibConfig++ library file!")
		ENDIF(NOT CONFIG++_LIBRARY)
	ENDIF(CONFIG++_FIND_REQUIRED)
ENDIF(CONFIG++_FOUND)


================================================
FILE: cmake/FindTinyXML2.cmake
================================================
FIND_PATH(TINYXML2_INCLUDE_DIR tinyxml2.h /usr/include /usr/local/include)

FIND_LIBRARY(TINYXML2_LIBRARY NAMES tinyxml2 PATH /usr/lib /usr/local/lib)

IF(TINYXML2_INCLUDE_DIR AND TINYXML2_LIBRARY)
	SET(TINYXML2_FOUND TRUE)
ENDIF(TINYXML2_INCLUDE_DIR AND TINYXML2_LIBRARY)

IF(TINYXML2_FOUND)
	IF(NOT TINYXML2_FIND_QUIETLY)
		MESSAGE(STATUS "Found TinyXML2: ${TINYXML2_LIBRARY}")
	ENDIF(NOT TINYXML2_FIND_QUIETLY)
ELSE(TINYXML2_FOUND)
	IF(TINYXML2_FIND_REQUIRED)
		IF(NOT TINYXML2_INCLUDE_DIR)
			MESSAGE(FATAL_ERROR "Could not find TinyXML2 header file!")
		ENDIF(NOT TINYXML2_INCLUDE_DIR)

		IF(NOT TINYXML2_LIBRARY)
			MESSAGE(FATAL_ERROR "Could not find TinyXML2 library files!")
		ENDIF(NOT TINYXML2_LIBRARY)
	ENDIF(TINYXML2_FIND_REQUIRED)
ENDIF(TINYXML2_FOUND)


================================================
FILE: cmake/FindUDev.cmake
================================================
FIND_PATH(UDEV_INCLUDE_DIR libudev.h /usr/include /usr/local/include)

FIND_LIBRARY(UDEV_LIBRARY NAMES udev libudev PATH /usr/lib /usr/local/lib)

IF(UDEV_INCLUDE_DIR AND UDEV_LIBRARY)
	SET(UDEV_FOUND TRUE)
ENDIF(UDEV_INCLUDE_DIR AND UDEV_LIBRARY)

IF(UDEV_FOUND)
	IF(NOT UDEV_FIND_QUIETLY)
		MESSAGE(STATUS "Found UDev: ${UDEV_LIBRARY}")
	ENDIF(NOT UDEV_FIND_QUIETLY)
ELSE(UDEV_FOUND)
	IF(UDEV_FIND_REQUIRED)
		IF(NOT UDEV_INCLUDE_DIR)
			MESSAGE(FATAL_ERROR "Could not find UDev header file!")
		ENDIF(NOT UDEV_INCLUDE_DIR)

		IF(NOT UDEV_LIBRARY)
			MESSAGE(FATAL_ERROR "Could not find UDev library files!")
		ENDIF(NOT UDEV_LIBRARY)
	ENDIF(UDEV_FIND_REQUIRED)
ENDIF(UDEV_FOUND)


================================================
FILE: etc/sidewinderd.conf
================================================
# Configuration file for sidewinderd

# sidewinderd will run as the user specified here. It is not recommended to
# leave it at default value "root".
user = "root";

# If set to false, macro recording will not capture any delays.
capture_delays = true;

# Change the PID file path here, if you experience issues with the default path.
pid-file = "/var/run/sidewinderd.pid";

# Defines, whether sidewinderd should wait for the working directory to become
# available or not.
# If set to true, sidewinderd will infinitely poll and wait, until the working
# directory becomes available. This is needed in environments, where the
# specified user's home directory is encrypted and not available on boot.
#encrypted_workdir = false;

# You can set an alternative profile path here.
# If this setting is set, sidewinderd will no longer use your specified user's
# home directory for storing application data, but instead use this path.
#workdir = "/var/lib/sidewinderd";


================================================
FILE: etc/sidewinderd.service.in
================================================
[Unit]
Description=Support for Microsoft SideWinder X4 / X6 and Logitech G105 / G710+
After=multi-user.target

[Service]
ExecStart=${CMAKE_INSTALL_PREFIX}/bin/sidewinderd

[Install]
WantedBy=multi-user.target


================================================
FILE: src/CMakeLists.txt
================================================
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})

AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} ROOT_SRC)
AUX_SOURCE_DIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/core" CORE_SRC)
AUX_SOURCE_DIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/vendor/logitech" LOGITECH_SRC)
AUX_SOURCE_DIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/vendor/microsoft" MICROSOFT_SRC)
LIST(APPEND VENDOR_LIST ${LOGITECH_SRC} ${MICROSOFT_SRC})
LIST(APPEND SOURCE_LIST ${ROOT_SRC} ${CORE_SRC} ${VENDOR_LIST})

CONFIGURE_FILE("${PROJECT_SOURCE_DIR}/etc/sidewinderd.service.in" "${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service")

ADD_EXECUTABLE(${PROJECT_NAME} ${SOURCE_LIST} "${PROJECT_SOURCE_DIR}/etc/sidewinderd.conf" "${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service")

TARGET_LINK_LIBRARIES(${PROJECT_NAME} stdc++ config++ udev pthread tinyxml2)

INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin)
INSTALL(FILES "${PROJECT_SOURCE_DIR}/etc/sidewinderd.conf" DESTINATION /etc COMPONENT config)
INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/sidewinderd.service" DESTINATION lib/systemd/system)


================================================
FILE: src/core/device.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef DEVICE_CLASS_H
#define DEVICE_CLASS_H

#include <string>

struct Device {
		std::string vendor;
		std::string product;
		std::string name;

		enum class Driver {
			LogitechG103,
			LogitechG105,
			LogitechG710,
			SideWinder
		} driver;
};

#endif


================================================
FILE: src/core/device_manager.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <cstring>
#include <iostream>

#include <core/device_manager.hpp>
#include <vendor/logitech/g103.hpp>
#include <vendor/logitech/g105.hpp>
#include <vendor/logitech/g710.hpp>
#include <vendor/microsoft/sidewinder.hpp>

constexpr auto VENDOR_MICROSOFT =	"045e";
constexpr auto VENDOR_LOGITECH =	"046d";
constexpr auto TIMEOUT =		5000;
constexpr auto NUM_POLL =		1;

void DeviceManager::discover() {
	for (auto it : devices_) {
		struct Device device = it;
		struct sidewinderd::DevNode devNode;

		if (probe(&device, &devNode) > 0) {
			// TODO use a unique identifier
			auto it = connected_.find(device.product);

			// skip this loop, if device is already connected
			if (it != connected_.end()) {
				continue;
			}

			switch (device.driver) {
				case Device::Driver::LogitechG103: {
					auto keyboard = new LogitechG103(&device, &devNode, config_, process_);
					keyboard->connect();
					connected_[device.product] = std::unique_ptr<Keyboard>(keyboard);
					break;
				}
				case Device::Driver::LogitechG105: {
					auto keyboard = new LogitechG105(&device, &devNode, config_, process_);
					keyboard->connect();
					connected_[device.product] = std::unique_ptr<Keyboard>(keyboard);
					break;
				}
				case Device::Driver::LogitechG710: {
					auto keyboard = new LogitechG710(&device, &devNode, config_, process_);
					keyboard->connect();
					connected_[device.product] = std::unique_ptr<Keyboard>(keyboard);
					break;
				}
				case Device::Driver::SideWinder: {
					auto keyboard = new SideWinder(&device, &devNode, config_, process_);
					keyboard->connect();
					connected_[device.product] = std::unique_ptr<Keyboard>(keyboard);
					break;
				}
			}
		}
	}
}

int DeviceManager::monitor() {
	// create udev object
	udev_ = udev_new();

	if (!udev_) {
		std::cerr << "Can't create udev." << std::endl;

		return -1;
	}

	// set up monitor for /dev/hidraw and /dev/input/event*
	monitor_ = udev_monitor_new_from_netlink(udev_, "udev");
	udev_monitor_filter_add_match_subsystem_devtype(monitor_, "hidraw", NULL);
	udev_monitor_filter_add_match_subsystem_devtype(monitor_, "input", NULL);
	udev_monitor_enable_receiving(monitor_);

	// get file descriptor for the monitor, so we can use poll() on it
	fd_ = udev_monitor_get_fd(monitor_);

	// setup poll
	pfd_.fd = fd_;
	pfd_.events = POLLIN;

	// initial discovery of new devices
	discover();

	struct udev_device *dev;

	// run monitoring loop, until we receive a signal
	while (process_->isActive()) {
		poll(&pfd_, NUM_POLL, TIMEOUT);
		dev = udev_monitor_receive_device(monitor_);

		if (dev) {
			// filter out nullptr returns, else std::string() fails
			auto ret = udev_device_get_action(dev);

			if (ret) {
				std::string action(ret);

				if (action == "add") {
					discover();
				} else if (action == "remove") {
					// check for disconnected devices
					auto product = udev_device_get_property_value(dev, "ID_MODEL_ID");

					if (product) {
						unbind(product);
					}
				}
			}

			udev_device_unref(dev);
		}
	}

	udev_monitor_unref(monitor_);
	udev_unref(udev_);

	return 0;
}

int DeviceManager::probe(struct Device *device, struct sidewinderd::DevNode *devNode) {
	struct udev_device *dev;
	struct udev_enumerate *enumerate;
	struct udev_list_entry *devices, *entry;
	bool isFound = false;

	// create a list of devices in hidraw and input subsystems
	enumerate = udev_enumerate_new(udev_);
	udev_enumerate_add_match_subsystem(enumerate, "hidraw");
	udev_enumerate_add_match_subsystem(enumerate, "input");
	udev_enumerate_scan_devices(enumerate);
	devices = udev_enumerate_get_list_entry(enumerate);

	udev_list_entry_foreach(entry, devices) {
		const char *sysPath, *devNodePath;
		sysPath = udev_list_entry_get_name(entry);
		dev = udev_device_new_from_syspath(udev_, sysPath);
		auto hidraw = udev_device_get_subsystem(dev);

		// evaluation from left to right; used to filter out nullptr
		if (hidraw && std::string(hidraw) == std::string("hidraw")) {
			devNodePath = udev_device_get_devnode(dev);
			dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface");

			if (!dev) {
				std::cerr << "Unable to find parent device." << std::endl;
			}

			auto bInterfaceNumber = udev_device_get_sysattr_value(dev, "bInterfaceNumber");

			if (bInterfaceNumber && std::string(bInterfaceNumber) == std::string("01")) {
				dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
				auto idVendor = udev_device_get_sysattr_value(dev, "idVendor");
				auto idProduct = udev_device_get_sysattr_value(dev, "idProduct");

				if (idVendor && std::string(idVendor) == device->vendor
					&& idProduct && std::string(idProduct) == device->product) {
						devNode->hidraw = devNodePath;
				}
			}
		}

		auto input = udev_device_get_subsystem(dev);
		auto product = udev_device_get_property_value(dev, "ID_MODEL_ID");
		auto vendor = udev_device_get_property_value(dev, "ID_VENDOR_ID");
		auto interface = udev_device_get_property_value(dev, "ID_USB_INTERFACE_NUM");

		/* find correct /dev/input/event* file */
		if (input && std::string(input) == std::string("input")
			&& product && std::string(product) == device->product
			&& vendor && std::string(vendor) == device->vendor
			&& interface && std::string(interface) == "00"
			&& udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD")
			&& strstr(sysPath, "event")
			&& udev_device_get_parent_with_subsystem_devtype(dev, "usb", NULL)) {
				devNode->inputEvent = udev_device_get_devnode(dev);
				std::clog << "Found device: " << device->vendor << ":" << device->product << std::endl;
				isFound = true;
		}

		udev_device_unref(dev);
	}

	/* free the enumerator object */
	udev_enumerate_unref(enumerate);

	return isFound;
}

void DeviceManager::unbind(std::string product) {
	auto it = connected_.find(product);

	if (it != connected_.end() && it->second->isConnected()) {
		connected_.erase(product);
	}
}

DeviceManager::DeviceManager(libconfig::Config *config, Process *process) {
	// list of supported devices
	devices_ = {
		{VENDOR_MICROSOFT, "074b", "Microsoft SideWinder X6",
			Device::Driver::SideWinder},
		{VENDOR_MICROSOFT, "0768", "Microsoft SideWinder X4",
			Device::Driver::SideWinder},
		{VENDOR_LOGITECH, "c248", "Logitech G105",
			Device::Driver::LogitechG105},
		{VENDOR_LOGITECH, "c24b", "Logitech G103",
			Device::Driver::LogitechG103},
		{VENDOR_LOGITECH, "c24d", "Logitech G710+",
			Device::Driver::LogitechG710}
	};

	config_ = config;
	process_ = process;
	udev_ = nullptr;
	monitor_ = nullptr;
}

DeviceManager::~DeviceManager() {
	// remove all connected devices
	connected_.clear();

	if (udev_) {
		udev_unref(udev_);
	}
}


================================================
FILE: src/core/device_manager.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef DEVICE_MANAGER_CLASS_H
#define DEVICE_MANAGER_CLASS_H

#include <map>
#include <string>
#include <vector>

#include <libudev.h>
#include <poll.h>

#include <libconfig.h++>

#include <device_data.hpp>
#include <process.hpp>
#include <core/device.hpp>
#include <core/keyboard.hpp>

class DeviceManager {
	public:
		int monitor();
		DeviceManager(libconfig::Config *config, Process *process);
		~DeviceManager();

	private:
		int fd_;
		std::map<std::string, std::unique_ptr<Keyboard>> connected_;
		std::vector<Device> devices_;
		struct pollfd pfd_;
		struct udev *udev_;
		struct udev_monitor *monitor_;
		libconfig::Config *config_;
		Process *process_;
		void discover();
		int probe(struct Device *device, struct sidewinderd::DevNode *devNode);
		void unbind(std::string product);
};

#endif


================================================
FILE: src/core/hid_interface.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <iostream>

#include <linux/hidraw.h>

#include <sys/ioctl.h>

#include <core/hid_interface.hpp>

unsigned char HidInterface::getReport(unsigned char report) {
	unsigned char buf[2] {};
	buf[0] = report;
	int ret = ioctl(*fd_, HIDIOCGFEATURE(sizeof(buf)), buf);

	if (ret < 0) {
		std::cerr << "Error getting HID feature report." << std::endl;
	} else {
		std::cout << "getFeatureReport(" << static_cast<int>(report)
			  << ") returned: " << std::hex
			  << static_cast<int>(buf[0]) << " "
			  << static_cast<int>(buf[1]) <<  std::endl;
	}

	return buf[1];
}

void HidInterface::setReport(unsigned char report, unsigned char value) {
	unsigned char buf[2];
	/* buf[0] is Report ID, buf[1] is value */
	buf[0] = report;
	buf[1] = value;
	/* TODO: check return value */
	int ret = ioctl(*fd_, HIDIOCSFEATURE(sizeof(buf)), buf);

	if (ret < 0) {
		std::cerr << "Error setting HID feature report." << std::endl;
	}
}

HidInterface::HidInterface(int *fd) {
	fd_ = fd;
}


================================================
FILE: src/core/hid_interface.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef HID_INTERFACE_CLASS_H
#define HID_INTERFACE_CLASS_H

#include <string>

class HidInterface {
	public:
		unsigned char getReport(unsigned char report);
		void setReport(unsigned char report, unsigned char value);
		HidInterface(int *fd);

	private:
		int *fd_;
};

#endif


================================================
FILE: src/core/key.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <iostream>
#include <sstream>

#include "key.hpp"

/**
 * Assembles relative path to Macro file.
 */
std::string Key::getMacroPath(int profile) {
	std::stringstream macroPath;
	macroPath << "profile_" << profile + 1 << "/" << "s" << keyData_->index << ".xml";

	return macroPath.str();
}

/**
 * Constructor initializing structs.
 */
Key::Key(struct KeyData *keyData) {
	keyData_ = keyData;
}


================================================
FILE: src/core/key.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef KEY_CLASS_H
#define KEY_CLASS_H

#include <string>

/**
 * Struct for storing and passing key data.
 *
 * @var index key index
 */
struct KeyData {
	int index;

	/**
	 * Enum class to classify key type.
	 * 
	 * @var Unknown not classified type
	 * @var Macro key type for macro keys, e.g. S1 on Microsoft Sidewinder X4
	 * @var Extra key type for extra keys, e.g. Bank Switch key on Microsoft
	 * Sidewinder X4
	 */
	enum class KeyType {
		Unknown,
		Macro,
		Extra
	} type;
};

/**
 * Class representing a key.
 *
 * Currently, it only works for Macro keys.
 */
class Key {
	public:
		std::string getMacroPath(int profile);
		Key(struct KeyData *keyData);

	private:
		struct KeyData *keyData_;
};

#endif


================================================
FILE: src/core/keyboard.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <cstdio>
#include <ctime>
#include <iostream>
#include <sstream>
#include <thread>

#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>

#include <linux/hidraw.h>
#include <linux/input.h>

#include <sys/ioctl.h>
#include <sys/stat.h>

#include "keyboard.hpp"

constexpr auto TIMEOUT =	5000;

bool Keyboard::isConnected() {
	return isConnected_;
}

void Keyboard::connect() {
	isConnected_ = true;
	listenThread_ = std::thread(&Keyboard::listen, this);
}

void Keyboard::disconnect() {
	isConnected_ = false;
}

void Keyboard::setupPoll() {
	fds[0].fd = fd_;
	fds[0].events = POLLIN;
	/* ignore second fd for now */
	fds[1].fd = -1;
	fds[1].events = POLLIN;
}

/* TODO: interrupt and exit play_macro when any macro_key has been pressed */
void Keyboard::playMacro(std::string macroPath, VirtualInput *virtInput) {
	tinyxml2::XMLDocument xmlDoc;
	xmlDoc.LoadFile(macroPath.c_str());

	if(!xmlDoc.ErrorID()) {
		tinyxml2::XMLElement* root = xmlDoc.FirstChildElement("Macro");

		for (tinyxml2::XMLElement* child = root->FirstChildElement(); child; child = child->NextSiblingElement()) {
			if (child->Name() == std::string("KeyBoardEvent")) {
				bool isPressed;
				int key = std::atoi(child->GetText());
				child->QueryBoolAttribute("Down", &isPressed);
				virtInput->sendEvent(EV_KEY, key, isPressed);
			} else if (child->Name() == std::string("DelayEvent")) {
				int delay = std::atoi(child->GetText());
				struct timespec request, remain;
				/*
				 * value is given in milliseconds, so we need to split it into
				 * seconds and nanoseconds. nanosleep() is interruptable and saves
				 * the remaining sleep time.
				 */
				request.tv_sec = delay / 1000;
				delay = delay - (request.tv_sec * 1000);
				request.tv_nsec = 1000000L * delay;
				nanosleep(&request, &remain);
			}
		}
	}
}

/*
 * Macro recording captures delays by default. Use the configuration to disable
 * capturing delays.
 */
void Keyboard::recordMacro(std::string path, Led *ledRecord, const int keyRecord) {
	struct timeval prev;
	struct KeyData keyData;
	prev.tv_usec = 0;
	prev.tv_sec = 0;
	std::cout << "Start Macro Recording on " << devNode_.inputEvent << std::endl;
	process_->privilege();
	evfd_ = open(devNode_.inputEvent.c_str(), O_RDONLY | O_NONBLOCK);
	process_->unprivilege();

	if (evfd_ < 0) {
		std::cout << "Can't open input event file" << std::endl;
	}

	/* additionally monitor /dev/input/event* with poll */
	fds[1].fd = evfd_;
	tinyxml2::XMLDocument doc;
	tinyxml2::XMLNode* root = doc.NewElement("Macro");
	/* start root element "Macro" */
	doc.InsertFirstChild(root);

	bool isRecordMode = true;

	while (isRecordMode) {
		keyData = pollDevice(2);

		if (keyData.index == keyRecord && keyData.type == KeyData::KeyType::Extra) {
			ledRecord->off();
			isRecordMode = false;
		}

		struct input_event inev;
		read(evfd_, &inev, sizeof(struct input_event));

		if (inev.type == EV_KEY && inev.value != 2) {
			/* only capturing delays, if capture_delays is set to true */
			if (prev.tv_usec && config_->lookup("capture_delays")) {
				auto diff = (inev.time.tv_usec + 1000000 * inev.time.tv_sec) - (prev.tv_usec + 1000000 * prev.tv_sec);
				auto delay = diff / 1000;
				/* start element "DelayEvent" */
				tinyxml2::XMLElement* DelayEvent = doc.NewElement("DelayEvent");
				DelayEvent->SetText(static_cast<int>(delay));
				root->InsertEndChild(DelayEvent);
			}

			/* start element "KeyBoardEvent" */
			tinyxml2::XMLElement* KeyBoardEvent = doc.NewElement("KeyBoardEvent");

			if (inev.value) {
				KeyBoardEvent->SetAttribute("Down", true);
			} else {
				KeyBoardEvent->SetAttribute("Down", false);
			}

			KeyBoardEvent->SetText(inev.code);
			root->InsertEndChild(KeyBoardEvent);
			prev = inev.time;
		}
	}

	/* write XML document */
	if (doc.SaveFile(path.c_str())) {
		std::cout << "Error XML SaveFile" << std::endl;
	}

	std::cout << "Exit Macro Recording" << std::endl;
	/* remove event file from poll fds */
	fds[1].fd = -1;
	close(evfd_);
}

struct KeyData Keyboard::pollDevice(nfds_t nfds) {
	/*
	 * poll() checks the device for any activities and blocks the loop,
	 * either until an event has occured, or the timeout has been reached.
	 * This leads to a very efficient polling mechanism.
	 */
	poll(fds, nfds, TIMEOUT);

	// check, if device has been disconnected
	if (fds->revents & POLLHUP || fds->revents & POLLERR) {
		disconnect();

		return KeyData();
	}

	struct KeyData keyData = getInput();

	return keyData;
}

void Keyboard::listen() {
	while (process_->isActive() && isConnected()) {
		struct KeyData keyData = pollDevice(1);
		handleKey(&keyData);
	}
}

void Keyboard::handleRecordMode(Led *ledRecord, const int keyRecord) {
	bool isRecordMode = true;
	/* record LED solid light */
	ledRecord->on();

	while (isRecordMode) {
		struct KeyData keyData = pollDevice(1);

		if (keyData.type == KeyData::KeyType::Unknown
				|| !keyData.index) {
			/* skip iteration if event is unknown or index is 0 */
			continue;
		} else if (keyData.type == KeyData::KeyType::Macro) {
			/* record LED should blink */
			ledRecord->blink();
			isRecordMode = false;
			Key key(&keyData);
			recordMacro(key.getMacroPath(profile_), ledRecord, keyRecord);
		} else if (keyData.type == KeyData::KeyType::Extra) {
			/* deactivate Record LED */
			ledRecord->off();
			isRecordMode = false;

			if (keyData.index != keyRecord) {
				handleKey(&keyData);
			}
		}
	}
}

Keyboard::Keyboard(struct Device *device,
		sidewinderd::DevNode *devNode, libconfig::Config *config,
		Process *process) : hid_{&fd_} {
	config_ = config;
	process_ = process;
	device_ = *device;
	devNode_ = *devNode;
	virtInput_ = new VirtualInput(&device_, &devNode_, process_);
	profile_ = 0;
	isConnected_ = true;

	for (int i = MIN_PROFILE; i < MAX_PROFILE; i++) {
		std::stringstream profileFolderPath;
		profileFolderPath << "profile_" << i + 1;
		mkdir(profileFolderPath.str().c_str(), S_IRWXU);
	}

	/* open file descriptor with root privileges */
	process_->privilege();
	fd_ = open(devNode->hidraw.c_str(), O_RDWR | O_NONBLOCK);
	process_->unprivilege();

	/* TODO: destruct, if interface can't be accessed */
	if (fd_ < 0) {
		std::cout << "Can't open hidraw interface" << std::endl;
	}

	setupPoll();

	std::cerr << "Keyboard Constructor" << std::endl;
}

Keyboard::~Keyboard() {
	std::cerr << "Keyboard Destructor" << std::endl;

	delete virtInput_;
	close(fd_);
}


================================================
FILE: src/core/keyboard.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef KEYBOARD_CLASS_H
#define KEYBOARD_CLASS_H

#include <string>
#include <thread>

#include <poll.h>

#include <libconfig.h++>

#include <process.hpp>
#include <device_data.hpp>
#include <core/device.hpp>
#include <core/hid_interface.hpp>
#include <core/key.hpp>
#include <core/led.hpp>
#include <core/virtual_input.hpp>

/* constants */
const int MAX_BUF = 8;
const int MIN_PROFILE = 0;
const int MAX_PROFILE = 3;

class Keyboard {
	public:
		bool isConnected();
		void connect();
		void disconnect();
		void listen();
		Keyboard(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
		virtual ~Keyboard();

	protected:
		bool isConnected_;
		int profile_;
		int fd_, evfd_;
		std::thread listenThread_;
		Process *process_;
		struct pollfd fds[2];
		struct Device device_;
		libconfig::Config *config_;
		sidewinderd::DevNode devNode_;
		HidInterface hid_;
		VirtualInput *virtInput_;
		virtual struct KeyData getInput() = 0;
		void setupPoll();
		static void playMacro(std::string macroPath, VirtualInput *virtInput);
		void recordMacro(std::string path, Led *ledRecord, const int keyRecord);
		struct KeyData pollDevice(nfds_t nfds);
		virtual void handleKey(struct KeyData *keyData) = 0;
		void handleRecordMode(Led *ledRecord, const int keyRecord);
};

#endif


================================================
FILE: src/core/led.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <iostream>
#include <core/led.hpp>

void Led::on() {
	auto report = hid_->getReport(report_);
	auto buf = report;

	if (type_ == LedType::Profile) {
		// clear out all LEDs, but Indicator LEDs
		buf &= group_->getIndicatorMask();
	}

	buf |= led_;

	// don't call, if there are no changes
	if (buf != report) {
		hid_->setReport(report_, buf);
	}
}

void Led::off() {
	auto buf = hid_->getReport(report_);
	buf &= ~led_;
	hid_->setReport(report_, buf);
}

void Led::blink() {
	if (blink_) {
		auto buf = hid_->getReport(report_);
		buf &= ~led_;
		buf |= blink_;
		hid_->setReport(report_, buf);
	} else {
		/*
		 * TODO Implement non-blocking software emulated blink, using
		 * std::thread and std::chrono. It should start blinking, when
		 * Led::blink() has been called and stop, when Led::off() has
		 * been called. 1 second on, 1 second off sounds like a good
		 * rhythm. It would be perfect, if one could interrupt the blink
		 * anytime, even in a this_thread::wait_for().
		 * Until it's implemented, let's use a solid light.
		 */
		on();
	}
}

void Led::registerBlink(unsigned char led) {
	blink_ = led;
}

void Led::setLedType(LedType type) {
	type_ = type;

	if (type_ == LedType::Indicator) {
		auto indicator= group_->getIndicatorMask();
		indicator |= led_;
		group_->setIndicatorMask(indicator);
	}
}

Led::Led(unsigned char report, unsigned char led, LedGroup *group) {
	report_ = report;
	led_ = led;
	group_ = group;
	blink_ = 0;
	type_ = LedType::Common;
	hid_ = group_->getHidInterface();

	// initial LED state is off
	off();
}


================================================
FILE: src/core/led.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef LED_CLASS_H
#define LED_CLASS_H

#include <core/hid_interface.hpp>
#include <core/led_group.hpp>

enum class LedType {
	Common,		// common LEDs don't have special handling
	Profile,	// profile LEDs are exclusively lit
	Indicator	// indicator LEDs are not allowed to be overwritten
};

class Led {
	public:
		/**
		 * Turns LED on.
		 */
		void on();

		/**
		 * Turns LED off.
		 */
		void off();

		/**
		 * Sets LED to blinking mode. Falls back to software emulation,
		 * if there is no hardware support.
		 */
		void blink();

		/**
		 * If LED supports blinking via hardware, set report ID and
		 * value using this function.
		 * @param led blink specific HID report value
		 */
		void registerBlink(unsigned char led);

		/**
		 * Sets LedType.
		 * @param type LedType can be Common, Profile or Indicator.
		 */
		void setLedType(LedType type);
		Led(unsigned char report, unsigned char led, LedGroup *group);

	private:
		unsigned char report_;
		unsigned char led_;
		unsigned char blink_;
		LedGroup *group_;
		LedType type_;
		HidInterface *hid_;
};

#endif


================================================
FILE: src/core/led_group.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <core/led_group.hpp>

unsigned char LedGroup::getIndicatorMask() {
	return indicator_;
}

void LedGroup::setIndicatorMask(unsigned char indicator) {
	indicator_ = indicator;
}

HidInterface *LedGroup::getHidInterface() {
	return hid_;
}

LedGroup::LedGroup(HidInterface *hid) {
	hid_ = hid;
	indicator_ = 0;
}


================================================
FILE: src/core/led_group.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef LED_GROUP_CLASS_H
#define LED_GROUP_CLASS_H

#include <core/hid_interface.hpp>

class LedGroup {
	public:
		unsigned char getIndicatorMask();
		void setIndicatorMask(unsigned char indicator);
		HidInterface *getHidInterface();
		LedGroup(HidInterface *hid);

	private:
		unsigned char indicator_;
		HidInterface *hid_;
};

#endif


================================================
FILE: src/core/virtual_input.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <cstdio>
#include <iostream>

#include <fcntl.h>
#include <unistd.h>

#include <linux/uinput.h>

#include <sys/ioctl.h>

#include "virtual_input.hpp"

/**
 * Method for sending input events to the operating system.
 *
 * @param type type of input event, e.g. EV_KEY
 * @param code keycode defined in header file input.h
 * @param value value the event carries, e.g. EV_KEY: 0 represents release, 1
 * keypress and 2 autorepeat
 */
void VirtualInput::sendEvent(short type, short code, int value) {
	struct input_event inev = input_event();
	inev.type = type;
	inev.code = code;
	inev.value = value;
	write(uifd_, &inev, sizeof(struct input_event));
	inev.type = EV_SYN;
	inev.code = 0;
	inev.value = 0;
	write(uifd_, &inev, sizeof(struct input_event));
}

/**
 * Constructor setting up operating system specific back-ends.
 */
VirtualInput::VirtualInput(struct Device *device, sidewinderd::DevNode *devNode, Process *process) {
	process_ = process;
	device_ = device;
	devNode_ = devNode;
	/* for Linux */
	createUidev();
}

VirtualInput::~VirtualInput() {
	close(uifd_);
}

/**
 * Creating a uinput virtual input device under Linux.
 */
void VirtualInput::createUidev() {
	/* open uinput device with root privileges */
	process_->privilege();
	uifd_ = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

	if (uifd_ < 0) {
		uifd_ = open("/dev/input/uinput", O_WRONLY | O_NONBLOCK);

		if (uifd_ < 0) {
			std::cout << "Can't open uinput" << std::endl;
		}
	}

	process_->unprivilege();
	/* set all keybits */
	ioctl(uifd_, UI_SET_EVBIT, EV_KEY);

	for (int i = KEY_ESC; i <= KEY_KPDOT; i++) {
		ioctl(uifd_, UI_SET_KEYBIT, i);
	}

	for (int i = KEY_ZENKAKUHANKAKU; i <= KEY_F24; i++) {
		ioctl(uifd_, UI_SET_KEYBIT, i);
	}

	for (int i = KEY_PLAYCD; i <= KEY_MICMUTE; i++) {
		ioctl(uifd_, UI_SET_KEYBIT, i);
	}

	/* uinput device details */
	struct uinput_user_dev uidev = uinput_user_dev();
	/* TODO: copy device's name */
	snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "Sidewinderd");
	uidev.id.bustype = BUS_USB;
	uidev.id.vendor = std::stoi(device_->vendor, nullptr, 16);
	uidev.id.product = std::stoi(device_->product, nullptr, 16);
	uidev.id.version = 1;
	/* write uinput device details */
	write(uifd_, &uidev, sizeof(struct uinput_user_dev));
	/* create uinput device */
	ioctl(uifd_, UI_DEV_CREATE);
}


================================================
FILE: src/core/virtual_input.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef VIRTUALINPUT_CLASS_H
#define VIRTUALINPUT_CLASS_H

#include <process.hpp>
#include <device_data.hpp>
#include <core/device.hpp>

/**
 * Class representing a virtual input device.
 *
 * Needed to send key events to the operating system. For Linux, uinput is used
 * as the back-end.
 */
class VirtualInput {
	public:
		void sendEvent(short type, short code, int value);
		VirtualInput(struct Device *device, sidewinderd::DevNode *devNode, Process *process);
		~VirtualInput();

	private:
		int uifd_; /**< uinput device file descriptor */
		Process *process_; /**< process object for setting privileges */
		Device *device_; /**< device information */
		sidewinderd::DevNode *devNode_; /**< device information */
		void createUidev();
};

#endif


================================================
FILE: src/device_data.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef DEVICEDATA_CLASS_H
#define DEVICEDATA_CLASS_H

#include <string>
#include <vector>

namespace sidewinderd {
	/**
	 * Struct for storing and passing paths to relevant /dev/<node> files.
	 */
	struct DevNode {
		std::string hidraw, inputEvent; /**< path to hidraw and input event */
	};
};

#endif


================================================
FILE: src/main.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <iostream>
#include <string>

#include <getopt.h>

#include <libconfig.h++>

#include <process.hpp>
#include <core/device_manager.hpp>

void help(std::string name) {
	std::cerr << "Usage: " << name << " [options]" << std::endl
		  << std::endl
		  << "Options:" << std::endl
		  << "  -c, --config=<file>   Override default configuration file path" << std::endl
		  << "  -d, --daemon          Run process as daemon" << std::endl
		  << "  -h, --help            Print this screen" << std::endl
		  << "  -v, --version         Print program version" << std::endl;
}

void setupConfig(libconfig::Config *config, std::string configFilePath = "/etc/sidewinderd.conf") {
	try {
		config->readFile(configFilePath.c_str());
	} catch (const libconfig::FileIOException &fioex) {
		std::cerr << "I/O error while reading file." << std::endl;
	} catch (const libconfig::ParseException &pex) {
		std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine() << " - " << pex.getError() << std::endl;
	}

	libconfig::Setting &root = config->getRoot();

	if (!root.exists("user")) {
		root.add("user", libconfig::Setting::TypeString) = "root";
	}

	if (!root.exists("capture_delays")) {
		root.add("capture_delays", libconfig::Setting::TypeBoolean) = true;
	}

	if (!root.exists("pid-file")) {
		root.add("pid-file", libconfig::Setting::TypeString) = "/var/run/sidewinderd.pid";
	}

	if (!root.exists("encrypted_workdir")) {
		root.add("encrypted_workdir", libconfig::Setting::TypeBoolean) = false;
	}
}

int main(int argc, char *argv[]) {
	/* object for managing runtime information */
	Process process;

	/* set program name */
	process.setName(argv[0]);

	/* handling command-line options */
	static struct option longOptions[] = {
		{"config", required_argument, 0, 'c'},
		{"daemon", no_argument, 0, 'd'},
		{"help", no_argument, 0, 'h'},
		{"version", no_argument, 0, 'v'},
		{0, 0, 0, 0}
	};

	int opt, index = 0;
	std::string configFilePath;

	/* flags */
	bool shouldDaemonize = false;

	while ((opt = getopt_long(argc, argv, ":c:dhv", longOptions, &index)) != -1) {
		switch (opt) {
			case 'c':
				configFilePath = optarg;
				break;
			case 'd':
				shouldDaemonize = true;
				break;
			case 'h':
				help(process.getName());
				return EXIT_SUCCESS;
			case 'v':
				std::cerr << process.getName() << " " << process.getVersion() << std::endl;
				return EXIT_SUCCESS;
			case ':':
				std::cerr << "Missing argument." << std::endl;
				return EXIT_FAILURE;
			case '?':
				std::cerr << "Unknown option." << std::endl;
				return EXIT_FAILURE;
			default:
				std::cerr << "Unexpected error." << std::endl;
				return EXIT_FAILURE;
		}
	}

	/* reading config file */
	libconfig::Config config;

	if (configFilePath.empty()) {
		setupConfig(&config);
	} else {
		setupConfig(&config, configFilePath);
	}

	/* daemonize, if flag has been set */
	if (shouldDaemonize) {
		int ret = process.daemonize();

		if (ret > 0) {
			return EXIT_SUCCESS;
		} else if (ret < 0) {
			return EXIT_FAILURE;
		}
	}

	/* creating pid file for single instance mechanism */
	if (process.createPid(config.lookup("pid-file"))) {
		return EXIT_FAILURE;
	}

	/* setting gid and uid to configured user */
	if (process.applyUser(config.lookup("user"))) {
		return EXIT_FAILURE;
	}

	// setting up working directory
	std::string workdir;

	if (config.exists("workdir")) {
		workdir = config.lookup("workdir").c_str();
	}

	if (process.createWorkdir(workdir, config.lookup("encrypted_workdir"))) {
		return EXIT_FAILURE;
	}

	std::clog << "Started sidewinderd." << std::endl;
	process.setActive(true);

	DeviceManager deviceManager(&config, &process);

	deviceManager.monitor();
	process.destroyPid();
	std::clog << "Stopped sidewinderd." << std::endl;

	return EXIT_SUCCESS;
}


================================================
FILE: src/process.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <chrono>
#include <csignal>
#include <iostream>
#include <thread>

#include <fcntl.h>
#include <unistd.h>

#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "process.hpp"

/* constants */
constexpr auto version =	"0.4.4";
constexpr auto wait =		1;

std::atomic<bool> Process::isActive_;

bool Process::isActive() {
	return isActive_;
}

void Process::setActive(bool isActive) {
	isActive_ = isActive;
}

std::string Process::getName() {
	if (name_.empty()) {
		name_ = "sidewinderd";
	}

	return name_;
}

void Process::setName(std::string name) {
	name_ = name;
}

int Process::daemonize() {
	pid_t pid, sid;
	pid = fork();

	if (pid < 0) {
		std::cerr << "Error creating daemon." << std::endl;
		return -1;
	}

	if (pid > 0) {
		return 1;
	}

	sid = setsid();

	if (sid < 0) {
		std::cerr << "Error setting sid." << std::endl;
		return -1;
	}

	pid = fork();

	if (pid < 0) {
		std::cerr << "Error forking second time." << std::endl;
		return -1;
	}

	if (pid > 0) {
		return 1;
	}

	umask(0);
	chdir("/");
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	return 0;
}

int Process::createPid(std::string pidPath) {
	pidPath_ = pidPath;
	pidFd_ = open(pidPath_.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

	if (pidFd_ < 0) {
		std::cerr << "PID file could not be created." << std::endl;

		return -1;
	}

	if (flock(pidFd_, LOCK_EX | LOCK_NB) < 0) {
		std::cerr << "Could not lock PID file, another instance is already running." << std::endl;
		close(pidFd_);

		return -1;
	}

	hasPid_ = true;

	return 0;
}

void Process::destroyPid() {
	if (pidFd_) {
		flock(pidFd_, LOCK_UN);
		close(pidFd_);
	}

	if (hasPid_) {
		unlink(pidPath_.c_str());
		hasPid_ = false;
	}
}

int Process::applyUser(std::string user) {
	user_ = user;
	pw_ = getpwnam(user_.c_str());

	if (pw_) {
		setegid(pw_->pw_gid);
		seteuid(pw_->pw_uid);
	} else {
		std::cerr << "User not found." << std::endl;

		return -1;
	}

	return 0;
}

int Process::createWorkdir(std::string directory, bool isEncrypted) {
	if (user_.empty()) {
		return 1;
	}

	std::string workdir;

	if (directory.empty()) {
		std::string xdgData;
		workdir = pw_->pw_dir;

		// find user-specific data directory
		if (const char *env = std::getenv("XDG_DATA_HOME")) {
			xdgData = env;

			if (!xdgData.empty()) {
				workdir = xdgData;
			}
		} else {
			// fallback to default
			xdgData = "/.local/share";
			workdir.append(xdgData);
		}
	} else {
		workdir = directory;
	}

	// wait until encrypted drive becomes available
	if (isEncrypted) {
		while (access(workdir.c_str(), F_OK)) {
			std::this_thread::sleep_for(std::chrono::seconds(wait));
		}
	}

	// creating sidewinderd directory
	if (directory.empty()) {
		workdir.append("/sidewinderd");
		mkdir(workdir.c_str(), S_IRWXU);
	}

	if (chdir(workdir.c_str())) {
		std::cerr << "Error accessing " << workdir << "." << std::endl;

		return -1;
	}

	return 0;
}

void Process::privilege() {
	seteuid(0);
}

void Process::unprivilege() {
	if (user_.empty()) {
		return;
	}

	seteuid(pw_->pw_uid);
}

std::string Process::getVersion() {
	return version;
}

void Process::sigHandler(int sig) {
	std::cerr << std::endl << "Stop signal received." << std::endl;

	switch(sig) {
		case SIGINT:
			setActive(false);
			break;
		case SIGTERM:
			setActive(false);
			break;
	}
}

Process::Process() {
	isActive_ = false;
	hasPid_ = false;
	pidFd_ = 0;

	/* signal handling */
	struct sigaction action;
	action.sa_handler = sigHandler;
	sigaction(SIGINT, &action, nullptr);
	sigaction(SIGTERM, &action, nullptr);
}

Process::~Process() {
	destroyPid();
}


================================================
FILE: src/process.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef PROCESS_CLASS_H
#define PROCESS_CLASS_H

#include <atomic>
#include <string>

#include <pwd.h>

class Process {
	public:
		static bool isActive();
		static void setActive(bool isActive);
		std::string getName();
		void setName(std::string name);
		int daemonize();
		int createPid(std::string pidPath);
		void destroyPid();
		int applyUser(std::string user);
		int createWorkdir(std::string directory, bool isEncrypted);
		void privilege();
		void unprivilege();
		std::string getVersion();
		Process();
		~Process();

	private:
		static std::atomic<bool> isActive_;
		bool hasPid_;
		int pidFd_;
		std::string name_;
		std::string user_;
		std::string pidPath_;
		struct passwd *pw_;
		static void sigHandler(int sig);
};

#endif


================================================
FILE: src/vendor/logitech/g103.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <cstdio>
#include <ctime>
#include <iostream>
#include <thread>

#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>

#include <linux/hidraw.h>
#include <linux/input.h>

#include <sys/ioctl.h>
#include <sys/stat.h>

#include "g103.hpp"

/* constants */
constexpr auto G103_FEATURE_REPORT_MACRO =	0x08;
constexpr auto G103_FEATURE_REPORT_MACRO_SIZE =	7;

void LogitechG103::setProfile(int profile) {
	profile_ = profile;
}

/*
 * get_input() checks, which keys were pressed. The macro keys are packed in a
 * 3-byte buffer.
 */
/*
 * TODO: only return latest pressed key, if multiple keys have been pressed at
 * the same time.
 */
struct KeyData LogitechG103::getInput() {
	struct KeyData keyData = KeyData();
	int key, nBytes;
	unsigned char buf[MAX_BUF];
	nBytes = read(fd_, buf, MAX_BUF);

	if (nBytes == 3 && buf[0] == 0x03) {
		/*
		 * cutting off buf[0], which is used to differentiate between macro and
		 * media keys. Our task is now to translate the buffer codes to
		 * something we can work with. Here is a table, where you can look up
		 * the keys and buffer, if you want to improve the current method:
		 *
		 * G1	0x03 0x01 0x00 - buf[1]
		 * G2	0x03 0x02 0x00 - buf[1]
		 * G3	0x03 0x04 0x00 - buf[1]
		 * G4	0x03 0x08 0x00 - buf[1]
		 * G5	0x03 0x10 0x00 - buf[1]
		 * G6	0x03 0x20 0x00 - buf[1]
		 */
		if (buf[2] == 0) {
			key = (static_cast<int>(buf[1]));
			key = ffs(key);

			if (key) {
				keyData.index = key;
				keyData.type = KeyData::KeyType::Macro;
			}
		}
	}

	return keyData;
}

void LogitechG103::handleKey(struct KeyData *keyData) {
	if (keyData->index != 0) {
		if (keyData->type == KeyData::KeyType::Macro) {
			Key key(keyData);
			std::string macroPath = key.getMacroPath(profile_);
			std::thread thread(playMacro, macroPath, virtInput_);
			thread.detach();
		}
	}
}

void LogitechG103::resetMacroKeys() {
	/* we need to zero out the report, so macro keys don't emit F-keys */
	unsigned char buf[G103_FEATURE_REPORT_MACRO_SIZE] = {};
	/* buf[0] is Report ID */
	buf[0] = G103_FEATURE_REPORT_MACRO;
	ioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);
}

LogitechG103::LogitechG103(struct Device *device,
		sidewinderd::DevNode *devNode, libconfig::Config *config,
		Process *process) :
		Keyboard::Keyboard(device, devNode, config, process) {
	resetMacroKeys();

	// set profile to default, as no profile switching is supported
	setProfile(0);
}

LogitechG103::~LogitechG103() {
	std::cerr << "LogitechG103 Destructor" << std::endl;

	// keyboard is not connected anymore, join the thread
	if (listenThread_.joinable()) {
		listenThread_.join();
	}
}


================================================
FILE: src/vendor/logitech/g103.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef LOGITECH_G103_CLASS_H
#define LOGITECH_G103_CLASS_H

#include <core/keyboard.hpp>

class LogitechG103 : public Keyboard {
	public:
		LogitechG103(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
		~LogitechG103();

	protected:
		struct KeyData getInput();
		void handleKey(struct KeyData *keyData);

	private:
		void setProfile(int profile);
		void resetMacroKeys();
};

#endif


================================================
FILE: src/vendor/logitech/g105.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <cstdio>
#include <ctime>
#include <iostream>
#include <thread>

#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>

#include <linux/hidraw.h>
#include <linux/input.h>

#include <sys/ioctl.h>
#include <sys/stat.h>

#include "g105.hpp"

/* constants */
constexpr auto G105_FEATURE_REPORT_LED =	0x06;
constexpr auto G105_FEATURE_REPORT_MACRO =	0x08;
constexpr auto G105_FEATURE_REPORT_MACRO_SIZE =	7;
constexpr auto G105_LED_M1 =			0x01;
constexpr auto G105_LED_M2 =			0x02;
constexpr auto G105_LED_M3 =			0x04;
constexpr auto G105_LED_MR =			0x08;
constexpr auto G105_KEY_M1 =			0x01;
constexpr auto G105_KEY_M2 =			0x02;
constexpr auto G105_KEY_M3 =			0x03;
constexpr auto G105_KEY_MR =			0x04;

void LogitechG105::setProfile(int profile) {
	profile_ = profile;

	switch (profile_) {
		case 0: ledProfile1_.on(); break;
		case 1: ledProfile2_.on(); break;
		case 2: ledProfile3_.on(); break;
	}
}

/*
 * get_input() checks, which keys were pressed. The macro keys are packed in a
 * 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.
 */
/*
 * TODO: only return latest pressed key, if multiple keys have been pressed at
 * the same time.
 */
struct KeyData LogitechG105::getInput() {
	struct KeyData keyData = KeyData();
	int key, nBytes;
	unsigned char buf[MAX_BUF];
	nBytes = read(fd_, buf, MAX_BUF);

	if (nBytes == 3 && buf[0] == 0x03) {
		/*
		 * cutting off buf[0], which is used to differentiate between macro and
		 * media keys. Our task is now to translate the buffer codes to
		 * something we can work with. Here is a table, where you can look up
		 * the keys and buffer, if you want to improve the current method:
		 *
		 * G1	0x03 0x01 0x00 - buf[1]
		 * G2	0x03 0x02 0x00 - buf[1]
		 * G3	0x03 0x04 0x00 - buf[1]
		 * G4	0x03 0x08 0x00 - buf[1]
		 * G5	0x03 0x10 0x00 - buf[1]
		 * G6	0x03 0x20 0x00 - buf[1]
		 * M1	0x03 0x00 0x01 - buf[2]
		 * M2	0x03 0x00 0x02 - buf[2]
		 * M3	0x03 0x00 0x04 - buf[2]
		 * MR	0x03 0x00 0x08 - buf[2]
		 */
		if (buf[2] == 0) {
			key = (static_cast<int>(buf[1]));
			key = ffs(key);

			if (key) {
				keyData.index = key;
				keyData.type = KeyData::KeyType::Macro;
			}
		} else if (buf[1] == 0) {
			key = (static_cast<int>(buf[2]));
			key = ffs(key);

			if (key) {
				keyData.index = key;
				keyData.type = KeyData::KeyType::Extra;
			}
		}
	}

	return keyData;
}

void LogitechG105::handleKey(struct KeyData *keyData) {
	if (keyData->index != 0) {
		if (keyData->type == KeyData::KeyType::Macro) {
			Key key(keyData);
			std::string macroPath = key.getMacroPath(profile_);
			std::thread thread(playMacro, macroPath, virtInput_);
			thread.detach();
		} else if (keyData->type == KeyData::KeyType::Extra) {
			if (keyData->index == G105_KEY_M1) {
				/* M1 key */
				setProfile(0);
			} else if (keyData->index == G105_KEY_M2) {
				/* M2 key */
				setProfile(1);
			} else if (keyData->index == G105_KEY_M3) {
				/* M3 key */
				setProfile(2);
			} else if (keyData->index == G105_KEY_MR) {
				/* MR key */
				handleRecordMode(&ledRecord_, G105_KEY_MR);
			}
		}
	}
}

void LogitechG105::resetMacroKeys() {
	/* we need to zero out the report, so macro keys don't emit numbers */
	unsigned char buf[G105_FEATURE_REPORT_MACRO_SIZE] = {};
	/* buf[0] is Report ID */
	buf[0] = G105_FEATURE_REPORT_MACRO;
	ioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);
}

LogitechG105::LogitechG105(struct Device *device,
		sidewinderd::DevNode *devNode, libconfig::Config *config,
		Process *process) :
		Keyboard::Keyboard(device, devNode, config, process),
		group_{&hid_},
		ledProfile1_{G105_FEATURE_REPORT_LED, G105_LED_M1, &group_},
		ledProfile2_{G105_FEATURE_REPORT_LED, G105_LED_M2, &group_},
		ledProfile3_{G105_FEATURE_REPORT_LED, G105_LED_M3, &group_},
		ledRecord_{G105_FEATURE_REPORT_LED, G105_LED_MR, &group_} {
	ledProfile1_.setLedType(LedType::Profile);
	ledProfile2_.setLedType(LedType::Profile);
	ledProfile3_.setLedType(LedType::Profile);
	ledRecord_.setLedType(LedType::Indicator);
	resetMacroKeys();

	// set initial LED
	ledProfile1_.on();
}

LogitechG105::~LogitechG105() {
	std::cerr << "LogitechG105 Destructor" << std::endl;

	// keyboard is not connected anymore, join the thread
	if (listenThread_.joinable()) {
		listenThread_.join();
	}
}


================================================
FILE: src/vendor/logitech/g105.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef LOGITECH_G105_CLASS_H
#define LOGITECH_G105_CLASS_H

#include <core/keyboard.hpp>
#include <core/led_group.hpp>

class LogitechG105 : public Keyboard {
	public:
		LogitechG105(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
		~LogitechG105();

	protected:
		struct KeyData getInput();
		void handleKey(struct KeyData *keyData);

	private:
		LedGroup group_;
		Led ledProfile1_;
		Led ledProfile2_;
		Led ledProfile3_;
		Led ledRecord_;
		void setProfile(int profile);
		void resetMacroKeys();
};

#endif


================================================
FILE: src/vendor/logitech/g710.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <cstdio>
#include <ctime>
#include <iostream>
#include <thread>

#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>

#include <linux/hidraw.h>
#include <linux/input.h>

#include <sys/ioctl.h>
#include <sys/stat.h>

#include "g710.hpp"

/* constants */
constexpr auto G710_FEATURE_REPORT_LED =	0x06;
constexpr auto G710_FEATURE_REPORT_MACRO =	0x09;
constexpr auto G710_FEATURE_REPORT_MACRO_SIZE =	13;
constexpr auto G710_LED_M1 =			0x10;
constexpr auto G710_LED_M2 =			0x20;
constexpr auto G710_LED_M3 =			0x40;
constexpr auto G710_LED_MR =			0x80;
constexpr auto G710_KEY_M1 =			0x01;
constexpr auto G710_KEY_M2 =			0x02;
constexpr auto G710_KEY_M3 =			0x03;
constexpr auto G710_KEY_MR =			0x04;

void LogitechG710::setProfile(int profile) {
	profile_ = profile;

	switch (profile_) {
		case 0: ledProfile1_.on(); break;
		case 1: ledProfile2_.on(); break;
		case 2: ledProfile3_.on(); break;
	}
}

/*
 * get_input() checks, which keys were pressed. The macro keys are packed in a
 * 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.
 */
/*
 * TODO: only return latest pressed key, if multiple keys have been pressed at
 * the same time.
 */
struct KeyData LogitechG710::getInput() {
	struct KeyData keyData = KeyData();
	int key, nBytes;
	unsigned char buf[MAX_BUF];
	nBytes = read(fd_, buf, MAX_BUF);

	if (nBytes == 4 && buf[0] == 0x03) {
		/*
		 * cutting off buf[0], which is used to differentiate between macro and
		 * media keys. Our task is now to translate the buffer codes to
		 * something we can work with. Here is a table, where you can look up
		 * the keys and buffer, if you want to improve the current method:
		 *
		 * G1	0x03 0x01 0x00 0x00 - buf[1]
		 * G2	0x03 0x02 0x00 0x00 - buf[1]
		 * G3	0x03 0x04 0x00 0x00 - buf[1]
		 * G4	0x03 0x08 0x00 0x00 - buf[1]
		 * G5	0x03 0x10 0x00 0x00 - buf[1]
		 * G6	0x03 0x20 0x00 0x00 - buf[1]
		 * M1	0x03 0x00 0x10 0x00 - buf[2]
		 * M2	0x03 0x00 0x20 0x00 - buf[2]
		 * M3	0x03 0x00 0x40 0x00 - buf[2]
		 * MR	0x03 0x00 0x80 0x00 - buf[2]
		 */
		if (buf[2] == 0) {
			key = (static_cast<int>(buf[1]));
			key = ffs(key);

			if (key) {
				keyData.index = key;
				keyData.type = KeyData::KeyType::Macro;
			}
		} else if (buf[1] == 0) {
			key = (static_cast<int>(buf[2])) >> 4;
			key = ffs(key);

			if (key) {
				keyData.index = key;
				keyData.type = KeyData::KeyType::Extra;
			}
		}
	}

	return keyData;
}

void LogitechG710::handleKey(struct KeyData *keyData) {
	if (keyData->index != 0) {
		if (keyData->type == KeyData::KeyType::Macro) {
			Key key(keyData);
			std::string macroPath = key.getMacroPath(profile_);
			std::thread thread(playMacro, macroPath, virtInput_);
			thread.detach();
		} else if (keyData->type == KeyData::KeyType::Extra) {
			if (keyData->index == G710_KEY_M1) {
				/* M1 key */
				setProfile(0);
			} else if (keyData->index == G710_KEY_M2) {
				/* M2 key */
				setProfile(1);
			} else if (keyData->index == G710_KEY_M3) {
				/* M3 key */
				setProfile(2);
			} else if (keyData->index == G710_KEY_MR) {
				/* MR key */
				handleRecordMode(&ledRecord_, G710_KEY_MR);
			}
		}
	}
}

void LogitechG710::resetMacroKeys() {
	/* we need to zero out the report, so macro keys don't emit numbers */
	unsigned char buf[G710_FEATURE_REPORT_MACRO_SIZE] = {};
	/* buf[0] is Report ID */
	buf[0] = G710_FEATURE_REPORT_MACRO;
	ioctl(fd_, HIDIOCSFEATURE(sizeof(buf)), buf);
}

LogitechG710::LogitechG710(struct Device *device,
		sidewinderd::DevNode *devNode, libconfig::Config *config,
		Process *process) :
		Keyboard::Keyboard(device, devNode, config, process),
		group_{&hid_},
		ledProfile1_{G710_FEATURE_REPORT_LED, G710_LED_M1, &group_},
		ledProfile2_{G710_FEATURE_REPORT_LED, G710_LED_M2, &group_},
		ledProfile3_{G710_FEATURE_REPORT_LED, G710_LED_M3, &group_},
		ledRecord_{G710_FEATURE_REPORT_LED, G710_LED_MR, &group_} {
	ledProfile1_.setLedType(LedType::Profile);
	ledProfile2_.setLedType(LedType::Profile);
	ledProfile3_.setLedType(LedType::Profile);
	ledRecord_.setLedType(LedType::Indicator);
	resetMacroKeys();

	// set initial LED
	ledProfile1_.on();
}

LogitechG710::~LogitechG710() {
	std::cerr << "LogitechG710 Destructor" << std::endl;

	// keyboard is not connected anymore, join the thread
	if (listenThread_.joinable()) {
		listenThread_.join();
	}
}


================================================
FILE: src/vendor/logitech/g710.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef LOGITECH_G710_PLUS_CLASS_H
#define LOGITECH_G710_PLUS_CLASS_H

#include <core/keyboard.hpp>
#include <core/led_group.hpp>

class LogitechG710 : public Keyboard {
	public:
		LogitechG710(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
		~LogitechG710();

	protected:
		struct KeyData getInput();
		void handleKey(struct KeyData *keyData);

	private:
		LedGroup group_;
		Led ledProfile1_;
		Led ledProfile2_;
		Led ledProfile3_;
		Led ledRecord_;
		void setProfile(int profile);
		void resetMacroKeys();
};

#endif


================================================
FILE: src/vendor/microsoft/sidewinder.cpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#include <cstdio>
#include <ctime>
#include <iostream>
#include <thread>

#include <fcntl.h>
#include <tinyxml2.h>
#include <unistd.h>

#include <linux/hidraw.h>
#include <linux/input.h>

#include <sys/ioctl.h>
#include <sys/stat.h>

#include "sidewinder.hpp"

/* constants */
constexpr auto SW_FEATURE_REPORT =	0x07;
constexpr auto SW_MACRO_PAD =		0x01;
constexpr auto SW_LED_AUTO =		0x02;
constexpr auto SW_LED_P1 =		0x04;
constexpr auto SW_LED_P2 =		0x08;
constexpr auto SW_LED_P3 =		0x10;
constexpr auto SW_LED_RECORD =		0x60;
constexpr auto SW_LED_RECORD_BLINK =	0x40;
constexpr auto SW_KEY_GAMECENTER =	0x10;
constexpr auto SW_KEY_RECORD =		0x11;
constexpr auto SW_KEY_PROFILE =		0x14;

void SideWinder::toggleMacroPad() {
	auto report = hid_.getReport(SW_FEATURE_REPORT);
	report ^= SW_MACRO_PAD;
	macroPad_ = report & SW_MACRO_PAD;
	hid_.setReport(SW_FEATURE_REPORT, report);
}

void SideWinder::switchProfile() {
	profile_ = (profile_ + 1) % MAX_PROFILE;

	switch (profile_) {
		case 0: ledProfile1_.on(); break;
		case 1: ledProfile2_.on(); break;
		case 2: ledProfile3_.on(); break;
	}
}

/*
 * get_input() checks, which keys were pressed. The macro keys are packed in a
 * 5-byte buffer, media keys (including Bank Switch and Record) use 8-bytes.
 */
/*
 * TODO: only return latest pressed key, if multiple keys have been pressed at
 * the same time.
 */
struct KeyData SideWinder::getInput() {
	struct KeyData keyData = KeyData();
	int key, nBytes;
	unsigned char buf[MAX_BUF];
	nBytes = read(fd_, buf, MAX_BUF);

	if (nBytes == 5 && buf[0] == 8) {
		/*
		 * cutting off buf[0], which is used to differentiate between macro and
		 * media keys. Our task is now to translate the buffer codes to
		 * something we can work with. Here is a table, where you can look up
		 * the keys and buffer, if you want to improve the current method:
		 *
		 * S1	0x08 0x01 0x00 0x00 0x00 - buf[1]
		 * S2	0x08 0x02 0x00 0x00 0x00 - buf[1]
		 * S3	0x08 0x04 0x00 0x00 0x00 - buf[1]
		 * S4	0x08 0x08 0x00 0x00 0x00 - buf[1]
		 * S5	0x08 0x10 0x00 0x00 0x00 - buf[1]
		 * S6	0x08 0x20 0x00 0x00 0x00 - buf[1]
		 * S7	0x08 0x40 0x00 0x00 0x00 - buf[1]
		 * S8	0x08 0x80 0x00 0x00 0x00 - buf[1]
		 * S9	0x08 0x00 0x01 0x00 0x00 - buf[2]
		 * S10	0x08 0x00 0x02 0x00 0x00 - buf[2]
		 * S11	0x08 0x00 0x04 0x00 0x00 - buf[2]
		 * S12	0x08 0x00 0x08 0x00 0x00 - buf[2]
		 * S13	0x08 0x00 0x10 0x00 0x00 - buf[2]
		 * S14	0x08 0x00 0x20 0x00 0x00 - buf[2]
		 * S15	0x08 0x00 0x40 0x00 0x00 - buf[2]
		 * S16	0x08 0x00 0x80 0x00 0x00 - buf[2]
		 * S17	0x08 0x00 0x00 0x01 0x00 - buf[3]
		 * S18	0x08 0x00 0x00 0x02 0x00 - buf[3]
		 * S19	0x08 0x00 0x00 0x04 0x00 - buf[3]
		 * S20	0x08 0x00 0x00 0x08 0x00 - buf[3]
		 * S21	0x08 0x00 0x00 0x10 0x00 - buf[3]
		 * S22	0x08 0x00 0x00 0x20 0x00 - buf[3]
		 * S23	0x08 0x00 0x00 0x40 0x00 - buf[3]
		 * S24	0x08 0x00 0x00 0x80 0x00 - buf[3]
		 * S25	0x08 0x00 0x00 0x00 0x01 - buf[4]
		 * S26	0x08 0x00 0x00 0x00 0x02 - buf[4]
		 * S27	0x08 0x00 0x00 0x00 0x04 - buf[4]
		 * S28	0x08 0x00 0x00 0x00 0x08 - buf[4]
		 * S29	0x08 0x00 0x00 0x00 0x10 - buf[4]
		 * S30	0x08 0x00 0x00 0x00 0x20 - buf[4]
		 */
		key = (static_cast<int>(buf[1]))
			| (static_cast<int>(buf[2]) << 8)
			| (static_cast<int>(buf[3]) << 16)
			| (static_cast<int>(buf[4]) << 24);
		key = ffs(key);

		if (key) {
			keyData.index = key;
			keyData.type = KeyData::KeyType::Macro;
		}
	} else if (nBytes == 8 && buf[0] == 1 && buf[6]) {
		/* buf[0] == 1 means media keys, buf[6] shows pressed key */
		keyData.index = buf[6];
		keyData.type = KeyData::KeyType::Extra;
	}

	return keyData;
}

void SideWinder::handleKey(struct KeyData *keyData) {
	if (keyData->type == KeyData::KeyType::Macro) {
		Key key(keyData);
		std::string macroPath = key.getMacroPath(profile_);
		std::thread thread(playMacro, macroPath, virtInput_);
		thread.detach();
	} else if (keyData->type == KeyData::KeyType::Extra) {
		if (keyData->index == SW_KEY_GAMECENTER) {
			toggleMacroPad();
		} else if (keyData->index == SW_KEY_RECORD) {
			handleRecordMode(&ledRecord_, SW_KEY_RECORD);
		} else if (keyData->index == SW_KEY_PROFILE) {
			switchProfile();
		}
	}
}

SideWinder::SideWinder(struct Device *device,
		sidewinderd::DevNode *devNode, libconfig::Config *config,
		Process *process) :
		Keyboard::Keyboard(device, devNode, config, process),
		group_{&hid_},
		ledProfile1_{SW_FEATURE_REPORT, SW_LED_P1, &group_},
		ledProfile2_{SW_FEATURE_REPORT, SW_LED_P2, &group_},
		ledProfile3_{SW_FEATURE_REPORT, SW_LED_P3, &group_},
		ledRecord_{SW_FEATURE_REPORT, SW_LED_RECORD, &group_},
		ledAuto_{SW_FEATURE_REPORT, SW_LED_AUTO, &group_} {
	ledProfile1_.setLedType(LedType::Profile);
	ledProfile2_.setLedType(LedType::Profile);
	ledProfile3_.setLedType(LedType::Profile);
	ledRecord_.setLedType(LedType::Indicator);
	ledRecord_.registerBlink(SW_LED_RECORD_BLINK);
	ledAuto_.setLedType(LedType::Indicator);

	// needed to avoid resetting macropad mode after bank switch
	// TODO: use a better solution after Led handling has been rewritten
	auto indicator= group_.getIndicatorMask();
	indicator |= SW_MACRO_PAD;
	group_.setIndicatorMask(indicator);

	// set initial LED
	ledProfile1_.on();
}

SideWinder::~SideWinder() {
	std::cerr << "SideWinder Destructor" << std::endl;

	// keyboard is not connected anymore, join the thread
	if (listenThread_.joinable()) {
		listenThread_.join();
	}
}


================================================
FILE: src/vendor/microsoft/sidewinder.hpp
================================================
/**
 * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>
 *
 * This source file is part of Sidewinder daemon and is distributed under the
 * MIT License. For more information, see LICENSE file.
 */

#ifndef MICROSOFT_SIDEWINDER_CLASS_H
#define MICROSOFT_SIDEWINDER_CLASS_H

#include <core/keyboard.hpp>
#include <core/led_group.hpp>

class SideWinder : public Keyboard {
	public:
		SideWinder(struct Device *device, sidewinderd::DevNode *devNode, libconfig::Config *config, Process *process);
		~SideWinder();

	protected:
		struct KeyData getInput();
		void handleKey(struct KeyData *keyData);

	private:
		LedGroup group_;
		Led ledProfile1_;
		Led ledProfile2_;
		Led ledProfile3_;
		Led ledRecord_;
		Led ledAuto_;
		unsigned char macroPad_;
		void toggleMacroPad();
		void switchProfile();
};

#endif
Download .txt
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
Download .txt
SYMBOL INDEX (93 symbols across 25 files)

FILE: src/core/device.hpp
  type Device (line 13) | struct Device {
    type Driver (line 18) | enum class Driver {

FILE: src/core/device_manager.cpp
  type Device (line 24) | struct Device
  type sidewinderd::DevNode (line 25) | struct sidewinderd::DevNode
  type udev_device (line 92) | struct udev_device
  type Device (line 128) | struct Device
  type sidewinderd::DevNode (line 128) | struct sidewinderd::DevNode
  type udev_device (line 129) | struct udev_device
  type udev_enumerate (line 130) | struct udev_enumerate
  type udev_list_entry (line 131) | struct udev_list_entry
  function udev_list_entry_foreach (line 141) | udev_list_entry_foreach(entry, devices) {

FILE: src/core/device_manager.hpp
  class DeviceManager (line 25) | class DeviceManager {
    type pollfd (line 35) | struct pollfd
    type udev (line 36) | struct udev
    type udev_monitor (line 37) | struct udev_monitor
    type Device (line 41) | struct Device
    type sidewinderd::DevNode (line 41) | struct sidewinderd::DevNode

FILE: src/core/hid_interface.hpp
  class HidInterface (line 13) | class HidInterface {

FILE: src/core/key.cpp
  type KeyData (line 26) | struct KeyData

FILE: src/core/key.hpp
  type KeyData (line 18) | struct KeyData {
    type KeyType (line 29) | enum class KeyType {
  class Key (line 41) | class Key {
    type KeyData (line 44) | struct KeyData
    type KeyData (line 47) | struct KeyData

FILE: src/core/keyboard.cpp
  type timespec (line 65) | struct timespec
  type timeval (line 85) | struct timeval
  type KeyData (line 86) | struct KeyData
  type input_event (line 115) | struct input_event
  type input_event (line 116) | struct input_event
  type KeyData (line 155) | struct KeyData
  type KeyData (line 170) | struct KeyData
  type KeyData (line 177) | struct KeyData
  type KeyData (line 188) | struct KeyData
  type Device (line 212) | struct Device

FILE: src/core/keyboard.hpp
  class Keyboard (line 31) | class Keyboard {
    type Device (line 37) | struct Device
    type pollfd (line 46) | struct pollfd
    type Device (line 47) | struct Device
    type KeyData (line 52) | struct KeyData
    type KeyData (line 56) | struct KeyData
    type KeyData (line 57) | struct KeyData

FILE: src/core/led.hpp
  type LedType (line 14) | enum class LedType {
  class Led (line 20) | class Led {

FILE: src/core/led_group.cpp
  function HidInterface (line 18) | HidInterface *LedGroup::getHidInterface() {

FILE: src/core/led_group.hpp
  class LedGroup (line 13) | class LedGroup {

FILE: src/core/virtual_input.cpp
  type input_event (line 29) | struct input_event
  type input_event (line 33) | struct input_event
  type input_event (line 37) | struct input_event
  type Device (line 43) | struct Device
  type uinput_user_dev (line 88) | struct uinput_user_dev
  type uinput_user_dev (line 96) | struct uinput_user_dev

FILE: src/core/virtual_input.hpp
  class VirtualInput (line 21) | class VirtualInput {
    type Device (line 24) | struct Device

FILE: src/device_data.hpp
  type sidewinderd (line 14) | namespace sidewinderd {
    type DevNode (line 18) | struct DevNode {

FILE: src/main.cpp
  function help (line 18) | void help(std::string name) {
  function setupConfig (line 28) | void setupConfig(libconfig::Config *config, std::string configFilePath =...
  function main (line 56) | int main(int argc, char *argv[]) {

FILE: src/process.cpp
  type sigaction (line 222) | struct sigaction

FILE: src/process.hpp
  class Process (line 16) | class Process {
    type passwd (line 40) | struct passwd

FILE: src/vendor/logitech/g103.cpp
  type KeyData (line 41) | struct KeyData
  type KeyData (line 42) | struct KeyData
  type KeyData (line 75) | struct KeyData
  type Device (line 94) | struct Device

FILE: src/vendor/logitech/g103.hpp
  class LogitechG103 (line 13) | class LogitechG103 : public Keyboard {
    type Device (line 15) | struct Device
    type KeyData (line 19) | struct KeyData
    type KeyData (line 20) | struct KeyData

FILE: src/vendor/logitech/g105.cpp
  type KeyData (line 56) | struct KeyData
  type KeyData (line 57) | struct KeyData
  type KeyData (line 102) | struct KeyData
  type Device (line 135) | struct Device

FILE: src/vendor/logitech/g105.hpp
  class LogitechG105 (line 14) | class LogitechG105 : public Keyboard {
    type Device (line 16) | struct Device
    type KeyData (line 20) | struct KeyData
    type KeyData (line 21) | struct KeyData

FILE: src/vendor/logitech/g710.cpp
  type KeyData (line 56) | struct KeyData
  type KeyData (line 57) | struct KeyData
  type KeyData (line 102) | struct KeyData
  type Device (line 135) | struct Device

FILE: src/vendor/logitech/g710.hpp
  class LogitechG710 (line 14) | class LogitechG710 : public Keyboard {
    type Device (line 16) | struct Device
    type KeyData (line 20) | struct KeyData
    type KeyData (line 21) | struct KeyData

FILE: src/vendor/microsoft/sidewinder.cpp
  type KeyData (line 63) | struct KeyData
  type KeyData (line 64) | struct KeyData
  type KeyData (line 126) | struct KeyData
  type Device (line 143) | struct Device

FILE: src/vendor/microsoft/sidewinder.hpp
  class SideWinder (line 14) | class SideWinder : public Keyboard {
    type Device (line 16) | struct Device
    type KeyData (line 20) | struct KeyData
    type KeyData (line 21) | struct KeyData
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (75K chars).
[
  {
    "path": ".gitignore",
    "chars": 26,
    "preview": "build\nCMakeLists.txt.user\n"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 294,
    "preview": "CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8)\nPROJECT(sidewinderd)\nSET(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wex"
  },
  {
    "path": "CONTRIBUTING",
    "chars": 1422,
    "preview": "Developer Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n660 Yor"
  },
  {
    "path": "LICENSE",
    "chars": 1140,
    "preview": "Sidewinder daemon is made available under the MIT License.\n\nCopyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n\nPer"
  },
  {
    "path": "README.md",
    "chars": 3032,
    "preview": "# Sidewinder daemon\n\nThis project provides support for gaming peripherals under Linux. It was\noriginally designed for th"
  },
  {
    "path": "cmake/FindConfig++.cmake",
    "chars": 775,
    "preview": "FIND_PATH(CONFIG++_INCLUDE_DIR libconfig.h++ /usr/include /usr/local/include)\n\nFIND_LIBRARY(CONFIG++_LIBRARY NAMES confi"
  },
  {
    "path": "cmake/FindTinyXML2.cmake",
    "chars": 767,
    "preview": "FIND_PATH(TINYXML2_INCLUDE_DIR tinyxml2.h /usr/include /usr/local/include)\n\nFIND_LIBRARY(TINYXML2_LIBRARY NAMES tinyxml2"
  },
  {
    "path": "cmake/FindUDev.cmake",
    "chars": 682,
    "preview": "FIND_PATH(UDEV_INCLUDE_DIR libudev.h /usr/include /usr/local/include)\n\nFIND_LIBRARY(UDEV_LIBRARY NAMES udev libudev PATH"
  },
  {
    "path": "etc/sidewinderd.conf",
    "chars": 965,
    "preview": "# Configuration file for sidewinderd\n\n# sidewinderd will run as the user specified here. It is not recommended to\n# leav"
  },
  {
    "path": "etc/sidewinderd.service.in",
    "chars": 209,
    "preview": "[Unit]\nDescription=Support for Microsoft SideWinder X4 / X6 and Logitech G105 / G710+\nAfter=multi-user.target\n\n[Service]"
  },
  {
    "path": "src/CMakeLists.txt",
    "chars": 1040,
    "preview": "INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})\n\nAUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} ROOT_SRC)\nAUX_SOURCE_"
  },
  {
    "path": "src/core/device.hpp",
    "chars": 463,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/device_manager.cpp",
    "chars": 6877,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/device_manager.hpp",
    "chars": 1008,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/hid_interface.cpp",
    "chars": 1182,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/hid_interface.hpp",
    "chars": 484,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/key.cpp",
    "chars": 607,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/key.hpp",
    "chars": 921,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/keyboard.cpp",
    "chars": 6594,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/keyboard.hpp",
    "chars": 1522,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/led.cpp",
    "chars": 1767,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/led.hpp",
    "chars": 1283,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/led_group.cpp",
    "chars": 524,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/led_group.hpp",
    "chars": 543,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/virtual_input.cpp",
    "chars": 2516,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/core/virtual_input.hpp",
    "chars": 958,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/device_data.hpp",
    "chars": 509,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/main.cpp",
    "chars": 3972,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/process.cpp",
    "chars": 3831,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/process.hpp",
    "chars": 944,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/vendor/logitech/g103.cpp",
    "chars": 2811,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/vendor/logitech/g103.hpp",
    "chars": 642,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/vendor/logitech/g105.cpp",
    "chars": 4466,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/vendor/logitech/g105.hpp",
    "chars": 769,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/vendor/logitech/g710.cpp",
    "chars": 4522,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/vendor/logitech/g710.hpp",
    "chars": 779,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/vendor/microsoft/sidewinder.cpp",
    "chars": 5581,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  },
  {
    "path": "src/vendor/microsoft/sidewinder.hpp",
    "chars": 812,
    "preview": "/**\n * Copyright (c) 2014 - 2016 Tolga Cakir <tolga@cevel.net>\n *\n * This source file is part of Sidewinder daemon and i"
  }
]

About this extraction

This page contains the full source code of the tolga9009/sidewinderd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (65.7 KB), approximately 20.5k tokens, and a symbol index with 93 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!