Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for third party joycons and xbox orientation #116

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "moonlight"]
path = moonlight
url = [email protected]:/lainproliant/moonlight
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.13)
project(joycond)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)

# Generate compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
Expand All @@ -14,6 +14,7 @@ add_executable(joycond "")
target_compile_options(joycond PRIVATE -Wall -Werror)
include_directories(
include/
moonlight/include/
${LIBEVDEV_INCLUDE_DIRS}
${LIBUDEV_INCLUDE_DIRS}
)
Expand All @@ -29,7 +30,7 @@ install(TARGETS joycond DESTINATION /usr/bin/
PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
)
install(FILES udev/89-joycond.rules udev/72-joycond.rules DESTINATION /lib/udev/rules.d/
PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ
PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ
)
install(FILES systemd/joycond.service DESTINATION /etc/systemd/system
PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ hid-nintendo is currently in review on the linux-input mailing list. The most re
# Installation
1. clone the repo
2. Install requirements (`sudo apt install libevdev-dev` or `sudo dnf install libevdev-devel libudev-devel`)
3. `cmake .`
4. `sudo make install`
5. `sudo systemctl enable --now joycond`
3. `git submodule update --init --recursive`
4. `cmake .`
5. `sudo make install`
6. `sudo systemctl enable --now joycond`

# Usage
When a joy-con or pro controller is connected via bluetooth or USB, the player LEDs should start blinking periodically. This signals that the controller is in pairing mode.
Expand Down
1 change: 1 addition & 0 deletions build_deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ architecture="arm64"
install_root="$(pwd)/build/deb/joycond"
package_version="0.4-0"

git submodule update --init --recursive
mkdir -p "$install_root" || exit
cmake . || exit
make DESTDIR="$install_root" install || exit
Expand Down
33 changes: 33 additions & 0 deletions include/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef JOYCOND_CONFIG_H
#define JOYCOND_CONFIG_H

#include "phys_ctlr.h"
#include <map>

typedef std::pair<std::string, std::string> CombinedMACs;

struct ControllerProps {
bool xbox_orientation = false;
};

class Config {
public:
Config(const std::string& filename = "/etc/joycond.conf");
~Config() { }

static const Config& get();

std::optional<phys_ctlr::Model> get_mac_device_override(const std::string& mac_addr) const;
const ControllerProps& get_combined_joycons_props(const CombinedMACs& macs) const;
void set_combined_joycons_props(const CombinedMACs& macs, const ControllerProps& props);

private:
void process_config_file(std::istream& infile);
void process_config_line(const std::string& line);
void parse_and_set_controller_prop(ControllerProps& props, const std::string& name, const std::string& value) const;

std::map<std::string, phys_ctlr::Model> _mac_model_overrides;
std::map<CombinedMACs, ControllerProps> _combined_joycons_props;
};

#endif /* !JOYCOND_CONFIG_H */
4 changes: 4 additions & 0 deletions include/phys_ctlr.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
#include <libevdev/libevdev.h>
#include <optional>
#include <string>
#include <map>

class phys_ctlr
{
public:
enum class Model { Procon, Snescon, Left_Joycon, Right_Joycon, Unknown };
enum class PairingState { Pairing, Lone, Waiting, Horizontal, Virt_Procon };

static const std::map<std::string, Model>& models_by_name();
static const std::map<Model, std::string>& names_by_model();

private:
std::string devpath;
std::string devname;
Expand Down
2 changes: 2 additions & 0 deletions include/virt_ctlr_combined.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "virt_ctlr.h"
#include "phys_ctlr.h"
#include "epoll_mgr.h"
#include "config.h"

#include <libevdev/libevdev.h>
#include <map>
Expand All @@ -22,6 +23,7 @@ class virt_ctlr_combined : public virt_ctlr
std::map<int, std::pair<struct ff_effect, struct ff_effect>> rumble_effects;
std::string left_mac;
std::string right_mac;
ControllerProps props;

void relay_events(std::shared_ptr<phys_ctlr> phys);
void handle_uinput_event();
Expand Down
3 changes: 3 additions & 0 deletions joycond.conf.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mac_device 98:B6:E9:F8:6F:86 Left_Joycon
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the config file I'm currently using.

mac_device 98:B6:E9:33:EF:5C Right_Joycon
prop combined_joycons 98:B6:E9:F8:6F:86 98:B6:E9:33:EF:5C xbox_orientation 1
1 change: 1 addition & 0 deletions moonlight
Submodule moonlight added at c8f04d
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ target_sources(
joycond
PRIVATE
main.cpp
config.cpp
phys_ctlr.cpp
virt_ctlr.cpp
virt_ctlr_passthrough.cpp
Expand All @@ -12,4 +13,3 @@ target_sources(
ctlr_detector_udev.cpp
ctlr_mgr.cpp
)

131 changes: 131 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#include "config.h"
#include "moonlight/file.h"
#include "moonlight/shlex.h"

static const Config _config;
static const ControllerProps _default_props;

// ctor
Config::Config(const std::string& filename) {
try {
auto infile = moonlight::file::open_r(filename);
process_config_file(infile);

} catch (const moonlight::core::RuntimeError& e) {
std::cout << "Failed to open config file " << filename << ", skipping." << std::endl;
}
}

// public static
const Config& Config::get() {
return _config;
}

// public
std::optional<phys_ctlr::Model> Config::get_mac_device_override(const std::string& mac_addr) const {
auto iter = _mac_model_overrides.find(mac_addr);
if (iter == _mac_model_overrides.end()) {
return {};
}
return iter->second;
}

const ControllerProps& Config::get_combined_joycons_props(const CombinedMACs& macs) const {
auto iter = _combined_joycons_props.find(macs);
if (iter == _combined_joycons_props.end()) {
return _default_props;
}

return iter->second;
}

void Config::set_combined_joycons_props(const CombinedMACs& macs, const ControllerProps& props) {
_combined_joycons_props.insert({macs, props});
}

// private
void Config::process_config_file(std::istream& infile) {
std::string line;
while (std::getline(infile, line)) {
process_config_line(line);
}
}

// private
void Config::process_config_line(const std::string& line) {
auto tokens = moonlight::shlex::split(line);
if (tokens.size() < 1) {
return;
}

if (tokens[0].starts_with("#")) {
return;
}

if (tokens[0] == "mac_device") {
if (tokens.size() != 3) {
std::cout << "Malformed `mac_device` directive in config file." << std::endl;
return;
}

auto mac_address = tokens[1];
auto device_name = tokens[2];

auto iter = phys_ctlr::models_by_name().find(device_name);
if (iter == phys_ctlr::models_by_name().end()) {
std::cout << "Invalid device name in `mac_device` directive: " << device_name << std::endl;
return;
}

_mac_model_overrides[mac_address] = iter->second;


} else if (tokens[0] == "prop") {
if (tokens.size() < 5) {
std::cout << "Malformed `prop` directive in config file." << std::endl;
return;
}

auto controller_type = tokens[1];

if (controller_type == "combined_joycons") {
if (tokens.size() < 6) {
std::cout << "Malformed `prop` directive for `combined_joycons` controller in config file." << std::endl;
return;
}

auto left_mac = tokens[2];
auto right_mac = tokens[3];
auto name = tokens[4];
auto value = tokens[5];

std::cout << "Setting controller prop " << name << " to " << value << " for combined joycons " << left_mac << " and " << right_mac << std::endl;

auto props = get_combined_joycons_props({left_mac, right_mac});
parse_and_set_controller_prop(props, name, value);
set_combined_joycons_props({left_mac, right_mac}, props);

} else {
std::cout << "Unsupported controller type for `prop` directive: " << controller_type << std::endl;
return;
}

} else {
std::cout << "Unknown directive in config file: " << tokens[0] << std::endl;
}
}

// private
void Config::parse_and_set_controller_prop(ControllerProps& props, const std::string& name, const std::string& value) const {
if (name == "xbox_orientation") {
try {
props.xbox_orientation = std::stoi(value);

} catch (...) {
std::cout << "Failed to parse value for `xbox_orientation`." << std::endl;
return;
}
} else {
std::cout << "Unsupported property: " << name << std::endl;
}
}
3 changes: 1 addition & 2 deletions src/ctlr_detector_udev.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ ctlr_detector_udev::ctlr_detector_udev(ctlr_mgr& ctlr_manager, epoll_mgr& epoll_
udev_mon_fd = udev_monitor_get_fd(mon);

subscriber = std::make_shared<epoll_subscriber>(std::vector({udev_mon_fd}),
[=](int event_fd){epoll_event_callback(event_fd);});
[=, this](int event_fd){epoll_event_callback(event_fd);});
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implicit capture of this is deprecated in C++20, so I went ahead and updated it here and other places where this is captured in lambdas.

epoll_manager.add_subscriber(subscriber);

// Detect any existing controllers prior to daemon start
Expand Down Expand Up @@ -83,4 +83,3 @@ ctlr_detector_udev::~ctlr_detector_udev()
{
epoll_manager.remove_subscriber(subscriber);
}

2 changes: 1 addition & 1 deletion src/ctlr_mgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ void ctlr_mgr::add_ctlr(const std::string& devpath, const std::string& devname)
unpaired_controllers[devpath] = phys;
phys->blink_player_leds();
subscribers[devpath] = std::make_shared<epoll_subscriber>(std::vector({phys->get_fd()}),
[=](int event_fd){epoll_event_callback(event_fd);});
[=, this](int event_fd){epoll_event_callback(event_fd);});
epoll_manager.add_subscriber(subscribers[devpath]);
} else {
std::cerr << "Attempting to add existing phys_ctlr to controller manager\n";
Expand Down
Loading