Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Core functionality working well; user configs remain to be implemented before public release.
  • Loading branch information
v1993 committed Aug 2, 2023
0 parents commit aeb700c
Show file tree
Hide file tree
Showing 17 changed files with 1,698 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "subprojects/gcemuhook"]
path = subprojects/gcemuhook
url = https://github.com/v1993/gcemuhook.git
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Cemuhook UDP server for devices with modern Linux drivers - successor to original evdevhook

## Supported devices

* Nintendo Switch Joy-Cons
* Nintendo Switch Pro Controller
* DualShock 3 controller
* DualShock 4 controller
* DualSense controller

Please note that as of right now only Nintendo controllers were tested. DualShock
and Dual Sense *should* work - feedback would be very welcome.

## Configuration

No configuration is required to get started - just run the resulting binary and
it will expose all supported controllers!

However, if you want to tweak controller orientations, run server on a different
port, or use over four controllers at once by running multiple servers on
different ports, it's possible to do so

## Quick build guide

```bash
git clone --recursive https://github.com/v1993/evdevhook2.git
cd evdevhook2
meson --buildtype=release -Db_lto=true --prefix=/usr build
ninja -C build
# Optional
ninja -C build install
```

### Updating
```bash
cd evdevhook2
git pull
git submodule update --recursive --init
ninja -C build
# Optional
ninja -C build install
```

## Dependencies
* libudev
* libevdev
* GLib 2.50+
* zlib
* Vala 0.56+ and libgee-0.8 (Ubuntu and derivatives should use [Vala Next PPA](https://launchpad.net/~vala-team/+archive/ubuntu/next))
* meson and ninja
* GCC/Clang

65 changes: 65 additions & 0 deletions config-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# evdevhook2 configs

The idea is to split config into two files.

## Device class settings

Configures what devices are supported and how their axles map to standard orientation. Ships with application, is not supposed to be edited by end users unless they want to configure a new kind of device (which they should PR upstream anyways).

### Proposed syntax

```ini
# Nintendo Switch Pro Controller
[057e:2009]
AccelMapping=y+z-x+
GyroMapping=y-z-x+
GyroSensitivity=0.858
```

Section name corresponds to VID:PID pair in hexadecimal form while other options are described below:

* `AccelMapping` string - how accelerometer maps from evdev axles to DSU ones
* `GyroMapping` string (optional but recommended) - how gyroscope maps from evdev axles to DSU ones
* `GyroSensitivity` double (optional) - multiplier for gyrocope inputs, fixes issues faced by some drivers (notably hid_nintendo)

## Per-device settings

This is an optional file that can be supplied by users to further configure their devices. It closely corresponds to config file of linuxmotehook2.

### Proposed syntax

```ini
# Main section
[Evdevhook]
Port=26761
AllowlistMode=true

# Section for my left joycon when playing Citra
[D4:F0:57:3E:25:C1]
Orientation=sideways-left
```

Main section, named `Evdevhook`, contains the following options:

* `Port` integer (defaults to `26760`) - port to run server on
* `AllowlistMode` boolean (defaults to `false`) - only provide devices provided in config file; useful if >4 devices are present and multiple servers are required

Per-device sections, each named after device's `uniq` string (typically its MAC):

* `Orientation` enum - a final transformation to apply to device input, identical to that of linuxmotehook2 (copy description from its wiki)

## To consider - button/axis inputs?

With this rewrite it might be possible to supply button inputs in addition to motion data. How good of an idea it is is still debatable - unlike wiimotes, those devices have modern-day drivers and don't have associated issues, thus I'll only work on it if a good enough usecase is demonstrated. Required extensions to config files:

* Device class settings
* * `MainNodeName` string (optional) - name of node that provides button inputs
* * Possibly some sort of config to map buttons?
* Per-device settings, main section
* * `SendButtons` boolean (defaults to `false`) - if buttons should be sent for supported devices; deprecated due to latency issues

If this is to be implemented, an issue arises when it comes to combining data from two devices. As such, button data and motion data should be stored in temporary buffers (as part of a class). On their respective SYN packets data from that interface is copied into main field and DSU packet is generated.

## Further considerations - touchscreen inputs?

I don't own nor plan to get DS4/5 controllers, so this is unlikely to be implemented unless someone interested in such support shows up and is willing to collaborate. I'd be far more willing to implement this compared to button inputs, though, due to uniqueness of this interface.
16 changes: 16 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
project('evdevhook2', ['c', 'vala'],
version: '0.1.0',
meson_version: '>= 0.50.0',
default_options: [ 'warning_level=2',
],
)

gcemuhook_proj = subproject('gcemuhook', default_options: ['default_library=static'])
gcemuhook_dep = gcemuhook_proj.get_variable('gcemuhook_dep')

extra_vapi_dir = meson.current_source_dir() / 'vapi'
add_project_arguments(['--vapidir', extra_vapi_dir], language: 'vala')

subdir('src')

#install_data('ExampleConfig.ini', install_dir : get_option('datadir') / 'evdevhook2')
174 changes: 174 additions & 0 deletions src/Config.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/* Config.vala
*
* Copyright 2022 v1993 <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/

using Gee;

namespace Evdevhook {
errordomain ConfigError {
INVALID_DEVICE_TYPE_CONFIG
}

class DeviceTypeConfig: Object {
public int axis_map[Linux.Input.ABS_RZ + 1];
public bool axis_inversion[Linux.Input.ABS_RZ + 1];
public float gyro_sensitivity;

public void read_orientation(string str, int base_idx) throws Error {
if (str.length != 6) throw new ConfigError.INVALID_DEVICE_TYPE_CONFIG("orientation string must be 6 characters long");

for (uint8 i = 0; i < 3; ++i) {
int evdev_axis;
bool invert;

switch (str[2 * i]) {
case 'x':
case 'X':
evdev_axis = base_idx;
break;
case 'y':
case 'Y':
evdev_axis = base_idx + 1;
break;
case 'z':
case 'Z':
evdev_axis = base_idx + 2;
break;
default:
throw new ConfigError.INVALID_DEVICE_TYPE_CONFIG("invalid letter in orientation specifier");
}

switch (str[2 * i + 1]) {
case '+':
invert = false;
break;
case '-':
invert = true;
break;
default:
throw new ConfigError.INVALID_DEVICE_TYPE_CONFIG("invalid sign in orientation specifier");
}

if (axis_map[evdev_axis] != -1)
throw new ConfigError.INVALID_DEVICE_TYPE_CONFIG("trying to map same evdev device axis (%i) to multiple DSU ones", evdev_axis);

axis_map[evdev_axis] = base_idx + i;
axis_inversion[evdev_axis] = invert;
}
}

construct {
axis_map = {-1, -1, -1, -1, -1, -1};
axis_inversion = {false, false, false, false, false, false};
gyro_sensitivity = 1.0f;
}
}

private class DeviceTypeIdentifier: Object, Hashable<DeviceTypeIdentifier> {
public uint16 vid;
public uint16 pid;

public DeviceTypeIdentifier(uint16 vid, uint16 pid) {
this.vid = vid;
this.pid = pid;
}

public bool equal_to(DeviceTypeIdentifier o) {
return vid == o.vid && pid == o.pid;
}

public uint hash() {
return (((uint)vid) << 16) | (uint)pid;
}
}

class DeviceConfig: Object {
public Cemuhook.DeviceOrientation orientation = NORMAL;
}

[SingleInstance]
class Config: Object {
private const string MAIN_GROUP = "Evdevhook";

private HashMap<DeviceTypeIdentifier, DeviceTypeConfig> device_type_configs;
private HashMap<string, DeviceConfig> device_configs;

public uint16 port { get; private set; default = 26760; }
public bool allowlist_mode { get; private set; default = false; }

construct {
device_type_configs = new HashMap<DeviceTypeIdentifier, DeviceTypeConfig>();
device_configs = new HashMap<string, DeviceConfig>();
}

public void init_device_types() throws Error {
var kfile = new KeyFile();
kfile.load_from_bytes(resources_lookup_data("/org/v1993/evdevhook2/DeviceTypes.ini", NONE), NONE);

// There's no main config group in this file.
// This config is not meant to be modified, so we terminate on errors.

foreach (unowned string group in kfile.get_groups()) {
var regex = /^([[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]):([[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]])$/;
MatchInfo minfo;
if (!regex.match(group, 0, out minfo)) {
throw new ConfigError.INVALID_DEVICE_TYPE_CONFIG("Unidentified device type configuration group %s", group);
}

var vid = (uint16)uint64.parse(minfo.fetch(1), 16);
var pid = (uint16)uint64.parse(minfo.fetch(2), 16);
var devtypeid = new DeviceTypeIdentifier(vid, pid);
var devtypeconf = new DeviceTypeConfig();

foreach (unowned string key in kfile.get_keys(group)) {
switch(key) {
case "AccelMapping":
devtypeconf.read_orientation(kfile.get_string(group, key), Linux.Input.ABS_X);
break;
case "GyroMapping":
devtypeconf.read_orientation(kfile.get_string(group, key), Linux.Input.ABS_RX);
break;
case "GyroSensitivity":
devtypeconf.gyro_sensitivity = (float)kfile.get_double(group, key);
break;
default:
throw new ConfigError.INVALID_DEVICE_TYPE_CONFIG("Unknown device type configuration key %s", key);
}
}

device_type_configs[devtypeid] = devtypeconf;
}
}

// TODO: load per-device (and main section) settings from config file

public DeviceTypeConfig? get_device_type_config(uint16 vid, uint16 pid) {
var devtypeid = new DeviceTypeIdentifier(vid, pid);
return device_type_configs.has_key(devtypeid) ? device_type_configs[devtypeid] : null;
}

public DeviceConfig? get_device_config(string uniq) {
if (device_configs.has_key(uniq)) {
return device_configs[uniq];
}

return allowlist_mode ? null : new DeviceConfig();
}
}
}
49 changes: 49 additions & 0 deletions src/DeviceTypes.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# This file defines accelerometer and gyro mappings for different devices.
# It is embedded directly into executable and editing it is usually DISCOURAGED unless you want to add support for a new device.
# In that case please consider opening a pull request with your added configuration.
# Otherwise, use a per-device config file instead. If it doesn't fit your needs open an issue.
#
# You can set environment variable
# G_RESOURCE_OVERLAYS='/org/v1993/evdevhook2/DeviceTypes.ini=/path/to/your/replacement/file.ini'
# when running evdevhook2 if you want to test your changes without recompiling.

# ### Nintendo ###

# Nintendo Switch Left Joy-Con
[057e:2006]
AccelMapping=y+z-x+
GyroMapping=y-z-x+
GyroSensitivity=0.858

# Nintendo Switch Right Joy-Con
[057e:2007]
AccelMapping=y+z-x+
GyroMapping=y-z-x+
GyroSensitivity=0.858

# Nintendo Switch Pro Controller
[057e:2009]
AccelMapping=y+z-x+
GyroMapping=y-z-x+
GyroSensitivity=0.858

# ### Sony ###

# DualShock 3 (no gyroscope)
[054c:0268]
AccelMapping=x-y-z-

# DualShock 4
[054c:05c4]
AccelMapping=x-y-z-
GyroMapping=x-y-z-

# DualShock 4 (second gen)
[054c:09cc]
AccelMapping=x-y-z-
GyroMapping=x-y-z-

# DualSense (different gyroscope mapping)
[054c:0ce6]
AccelMapping=x-y-z-
GyroMapping=x+y-z-
Loading

0 comments on commit aeb700c

Please sign in to comment.