diff --git a/CMakeLists.txt b/CMakeLists.txt index 1766bee2c4..853f206cd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -541,6 +541,11 @@ if(ENABLE_EVDEV) endif() endif() +if(UNIX) + message("Using named pipes as controller inputs") + add_definitions(-DUSE_PIPES=1) +endif() + ######################################## # Setup include directories (and make sure they are preferred over the Externals) # diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index b0a8dcd5de..41cb65c24c 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -75,6 +75,7 @@ #define WII_WC24CONF_DIR "shared2" DIR_SEP "wc24" #define THEMES_DIR "Themes" #define ANAGLYPH_DIR "Anaglyph" +#define PIPES_DIR "Pipes" // Filenames // Files in the directory returned by GetUserPath(D_CONFIG_IDX) diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 67674cbef0..84961d54ad 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -790,6 +790,7 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_LOGS_IDX] = s_user_paths[D_USER_IDX] + LOGS_DIR DIR_SEP; s_user_paths[D_MAILLOGS_IDX] = s_user_paths[D_LOGS_IDX] + MAIL_LOGS_DIR DIR_SEP; s_user_paths[D_THEMES_IDX] = s_user_paths[D_USER_IDX] + THEMES_DIR DIR_SEP; + s_user_paths[D_PIPES_IDX] = s_user_paths[D_USER_IDX] + PIPES_DIR DIR_SEP; s_user_paths[F_DOLPHINCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DOLPHIN_CONFIG; s_user_paths[F_DEBUGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DEBUGGER_CONFIG; s_user_paths[F_LOGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + LOGGER_CONFIG; diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 3deb91dcd1..fefbed193d 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -41,6 +41,7 @@ enum { D_LOGS_IDX, D_MAILLOGS_IDX, D_THEMES_IDX, + D_PIPES_IDX, F_DOLPHINCONFIG_IDX, F_DEBUGGERCONFIG_IDX, F_LOGGERCONFIG_IDX, diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index 549865b8e9..b200625000 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -42,6 +42,10 @@ if(LIBEVDEV_FOUND AND LIBUDEV_FOUND) set(LIBS ${LIBS} ${LIBEVDEV_LIBRARY} ${LIBUDEV_LIBRARY}) endif() +if(UNIX) + set(SRCS ${SRCS} ControllerInterface/Pipes/Pipes.cpp) +endif() + if(SDL_FOUND OR SDL2_FOUND) set(SRCS ${SRCS} ControllerInterface/SDL/SDL.cpp) if (SDL2_FOUND) diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 0631069e16..93cd744003 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -29,6 +29,9 @@ #ifdef CIFACE_USE_EVDEV #include "InputCommon/ControllerInterface/evdev/evdev.h" #endif +#ifdef CIFACE_USE_PIPES + #include "InputCommon/ControllerInterface/Pipes/Pipes.h" +#endif using namespace ciface::ExpressionParser; @@ -75,6 +78,9 @@ void ControllerInterface::Initialize(void* const hwnd) #ifdef CIFACE_USE_EVDEV ciface::evdev::Init(m_devices); #endif +#ifdef CIFACE_USE_PIPES + ciface::Pipes::Init(m_devices); +#endif m_is_init = true; } diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 3aca18e95c..08b282dba8 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -38,6 +38,9 @@ #if defined(HAVE_LIBEVDEV) && defined(HAVE_LIBUDEV) #define CIFACE_USE_EVDEV #endif +#if defined(USE_PIPES) + #define CIFACE_USE_PIPES +#endif // // ControllerInterface diff --git a/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.cpp b/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.cpp new file mode 100644 index 0000000000..f2923a5395 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.cpp @@ -0,0 +1,180 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Common/MathUtil.h" +#include "Common/StringUtil.h" +#include "InputCommon/ControllerInterface/Pipes/Pipes.h" + +namespace ciface +{ +namespace Pipes +{ + +static const std::array s_button_tokens +{{ + "A", + "B", + "X", + "Y", + "Z", + "START", + "L", + "R", + "D_UP", + "D_DOWN", + "D_LEFT", + "D_RIGHT" +}}; + +static const std::array s_shoulder_tokens +{{ + "L", + "R" +}}; + +static const std::array s_axis_tokens +{{ + "MAIN", + "C" +}}; + +void Init(std::vector& devices) +{ + // Search the Pipes directory for files that we can open in read-only, + // non-blocking mode. The device name is the virtual name of the file. + File::FSTEntry fst; + int found = 0; + std::string dir_path = File::GetUserPath(D_PIPES_IDX); + if (!File::Exists(dir_path)) + return; + fst = File::ScanDirectoryTree(dir_path, false); + if (!fst.isDirectory) + return; + for (unsigned int i = 0; i < fst.size; ++i) + { + const File::FSTEntry& child = fst.children[i]; + if (child.isDirectory) + continue; + int fd = open(child.physicalName.c_str(), O_RDONLY | O_NONBLOCK); + if (fd < 0) + continue; + devices.push_back(new PipeDevice(fd, child.virtualName, found++)); + } +} + +PipeDevice::PipeDevice(int fd, const std::string& name, int id) + : m_fd(fd), m_name(name), m_id(id) +{ + for (const auto& tok : s_button_tokens) + { + PipeInput* btn = new PipeInput("Button " + tok); + AddInput(btn); + m_buttons[tok] = btn; + } + for (const auto& tok : s_shoulder_tokens) + { + AddAxis(tok, 0.0); + } + for (const auto& tok : s_axis_tokens) + { + AddAxis(tok + " X", 0.5); + AddAxis(tok + " Y", 0.5); + } +} + +PipeDevice::~PipeDevice() +{ + close(m_fd); +} + +void PipeDevice::UpdateInput() +{ + // Read any pending characters off the pipe. If we hit a newline, + // then dequeue a command off the front of m_buf and parse it. + char buf[32]; + ssize_t bytes_read = read(m_fd, buf, sizeof buf); + while (bytes_read > 0) + { + m_buf.append(buf, bytes_read); + bytes_read = read(m_fd, buf, sizeof buf); + } + std::size_t newline = m_buf.find("\n"); + std::size_t erase_until = 0; + while (newline != std::string::npos) + { + std::string command = m_buf.substr(0, newline); + ParseCommand(command); + erase_until = newline + 1; + newline = m_buf.find("\n", erase_until); + } + m_buf.erase(0, erase_until); +} + +void PipeDevice::AddAxis(const std::string& name, double value) +{ + // Dolphin uses separate axes for left/right, which complicates things. + PipeInput* ax_hi = new PipeInput("Axis " + name + " +"); + ax_hi->SetState(value); + PipeInput* ax_lo = new PipeInput("Axis " + name + " -"); + ax_lo->SetState(value); + m_axes[name + " +"] = ax_hi; + m_axes[name + " -"] = ax_lo; + AddAnalogInputs(ax_lo, ax_hi); +} + +void PipeDevice::SetAxis(const std::string& entry, double value) +{ + value = MathUtil::Clamp(value, 0.0, 1.0); + double hi = std::max(0.0, value - 0.5) * 2.0; + double lo = (0.5 - std::min(0.5, value)) * 2.0; + auto search_hi = m_axes.find(entry + " +"); + if (search_hi != m_axes.end()) + search_hi->second->SetState(hi); + auto search_lo = m_axes.find(entry + " -"); + if (search_lo != m_axes.end()) + search_lo->second->SetState(lo); +} + +void PipeDevice::ParseCommand(const std::string& command) +{ + std::vector tokens; + SplitString(command, ' ', tokens); + if (tokens.size() < 2 || tokens.size() > 4) + return; + if (tokens[0] == "PRESS" || tokens[0] == "RELEASE") + { + auto search = m_buttons.find(tokens[1]); + if (search != m_buttons.end()) + search->second->SetState(tokens[0] == "PRESS" ? 1.0 : 0.0); + } + else if (tokens[0] == "SET") + { + if (tokens.size() == 3) + { + double value = strtod(tokens[2].c_str(), nullptr); + SetAxis(tokens[1], (value / 2.0) + 0.5); + } + else + { + double x = strtod(tokens[2].c_str(), nullptr); + double y = strtod(tokens[3].c_str(), nullptr); + SetAxis(tokens[1] + " X", x); + SetAxis(tokens[1] + " Y", y); + } + } +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.h b/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.h new file mode 100644 index 0000000000..c3c2ea0063 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Pipes/Pipes.h @@ -0,0 +1,65 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace Pipes +{ +// To create a piped controller input, create a named pipe in the +// Pipes directory and write commands out to it. Commands are separated +// by a newline character, with spaces separating command tokens. +// Command syntax is as follows, where curly brackets are one-of and square +// brackets are inclusive numeric ranges. Cases are sensitive. Numeric inputs +// are clamped to [0, 1] and otherwise invalid commands are discarded. +// {PRESS, RELEASE} {A, B, X, Y, Z, START, L, R, D_UP, D_DOWN, D_LEFT, D_RIGHT} +// SET {L, R} [0, 1] +// SET {MAIN, C} [0, 1] [0, 1] + +void Init(std::vector& devices); + +class PipeDevice : public Core::Device +{ +public: + PipeDevice(int fd, const std::string& name, int id); + ~PipeDevice(); + + void UpdateInput() override; + std::string GetName() const override { return m_name; } + int GetId() const override { return m_id; } + std::string GetSource() const override { return "Pipe"; } + +private: + class PipeInput : public Input + { + public: + PipeInput(const std::string& name) : m_name(name), m_state(0.0) {} + std::string GetName() const override { return m_name; } + ControlState GetState() const override { return m_state; } + void SetState(ControlState state) { m_state = state; } + private: + const std::string m_name; + ControlState m_state; + }; + + void AddAxis(const std::string& name, double value); + void ParseCommand(const std::string& command); + void SetAxis(const std::string& entry, double value); + + const int m_fd; + const std::string m_name; + const int m_id; + std::string m_buf; + std::map m_buttons; + std::map m_axes; +}; +} +}