├── .gitignore ├── ds360-stop.sh ├── remove-udev-rule.sh ├── ds360.service ├── add-udev-rule.sh ├── 80-ds360.rules ├── Makefile ├── dualsense.xboxdrv ├── README.md └── ds360.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | ds360 2 | .vscode/ -------------------------------------------------------------------------------- /ds360-stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | kill -9 $(cat /tmp/ds360.pid) -------------------------------------------------------------------------------- /remove-udev-rule.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sudo rm -f /usr/lib/udev/rules.d/80-ds360.rules 3 | sudo rm -f /usr/bin/ds360-stop.sh 4 | sudo udevadm control --reload -------------------------------------------------------------------------------- /ds360.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ds360 service 3 | 4 | [Service] 5 | Type=simple 6 | ExecStart=%h/.local/bin/ds360 7 | 8 | [Install] 9 | WantedBy=default.target -------------------------------------------------------------------------------- /add-udev-rule.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sudo cp 80-ds360.rules /usr/lib/udev/rules.d/ 3 | sudo cp ds360-stop.sh /usr/bin/ 4 | sudo chmod 555 /usr/bin/ds360-stop.sh 5 | sudo udevadm control --reload -------------------------------------------------------------------------------- /80-ds360.rules: -------------------------------------------------------------------------------- 1 | ACTION=="add", SUBSYSTEM=="input", ATTRS{id/vendor}=="054c", ATTRS{id/product}=="0ce6", TAG+="systemd", ENV{SYSTEMD_USER_WANTS}="ds360.service" 2 | ACTION=="remove", SUBSYSTEM=="input", ATTRS{id/vendor}=="054c", ATTRS{id/product}=="0ce6", RUN+="/usr/bin/ds360-stop.sh" -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(PREFIX),) 2 | PREFIX := ~/.local 3 | endif 4 | 5 | ds360: ds360.cpp 6 | g++ -Wall -o ds360 ds360.cpp 7 | 8 | install: ds360 9 | install -m 555 ds360 $(PREFIX)/bin/ 10 | install -m 444 ds360.service $(HOME)/.config/systemd/user/ 11 | systemctl --user daemon-reload 12 | 13 | uninstall: 14 | rm -f $(PREFIX)/bin/ds360 15 | rm -f $(HOME)/.config/systemd/user/ds360.service 16 | systemctl --user daemon-reload 17 | sudo rm -f /usr/lib/udev/rules.d/80-ds360.rules 18 | sudo rm -f /usr/bin/ds360-stop.sh 19 | sudo udevadm control --reload -------------------------------------------------------------------------------- /dualsense.xboxdrv: -------------------------------------------------------------------------------- 1 | [xboxdrv] 2 | 3 | #evdev = /dev/input/by-id/usb-Sony_Interactive_Entertainment_Wireless_Controller-if03-event-joystick 4 | evdev-grab = true 5 | evdev-debug = false 6 | mimic-xpad = true 7 | silent = true 8 | quiet = true 9 | #deadzone = 5% 10 | #deadzone-trigger = 10% 11 | 12 | [axismap] 13 | 14 | -Y1 = Y1 15 | -Y2 = Y2 16 | 17 | [evdev-absmap] 18 | 19 | ABS_HAT0X = dpad_x 20 | ABS_HAT0Y = dpad_y 21 | ABS_X = X1 22 | ABS_Y = Y1 23 | ABS_RX = X2 24 | ABS_RY = Y2 25 | ABS_Z = LT 26 | ABS_RZ = RT 27 | 28 | [evdev-keymap] 29 | 30 | BTN_SOUTH = A 31 | BTN_EAST = B 32 | BTN_NORTH = Y 33 | BTN_WEST = X 34 | BTN_START = start 35 | BTN_MODE = guide 36 | BTN_SELECT = back 37 | BTN_TL = LB 38 | BTN_TR = RB 39 | BTN_TL2 = LT 40 | BTN_TR2 = RT 41 | BTN_THUMBL = TL 42 | BTN_THUMBR = TR 43 | 44 | # EOF # -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **Not maintained anymore** 2 | 3 | I'm sure someone would find a more elegant way of doing this than simply wrapping xboxdrv, latest Proton + Steam Input work perfectly for me, add non-steam shortcut if it doesn't work (Steam Deck work seems to have improved controller support). 4 | 5 | # Emulate Dualsense as a 360 controller on Linux 6 | 7 | Initially wanted to implement Sony's PlayStation 5 controller support to `sc-controller` for broader config options but couldn't be bothered with all the python debugging right now. 8 | 9 | Useful for games not recognizing the Dualsense for some reason, like RDR 2 or Dragon Quest XI or other non-steam games in my testing, emulating the controller as a 360 fixes the compatibility gap entirely so a Dualsense can be used as the ultimate controller. 10 | 11 | The program is mainly based on xboxdrv's excellent ability to emulate a 360 controller. 12 | 13 | ## Pre-requirements 14 | 15 | 16 | - Have a recent kernel (5.12+) that includes the latest official drivers (hid-playstation) 17 | - Install xboxdrv (https://xboxdrv.gitlab.io) 18 | - Basic dev tools like `g++` or `make` 19 | 20 | ## How to compile 21 | ``` 22 | git clone https://github.com/yoyossef/ds360.git 23 | cd ds360 24 | make 25 | ``` 26 | ## Usage 27 | #### Starting the process in terminal, press CTRL+C to stop 28 | ``` 29 | ./ds360 30 | ``` 31 | #### Starting ds360.service manually, controller must be connected 32 | ``` 33 | systemctl start --user ds360.service 34 | ``` 35 | #### Automatically start ds360.service via udev rule when controller connects 36 | ``` 37 | ./add-udev-rule.sh 38 | ``` 39 | ## How to install 40 | ``` 41 | git clone https://github.com/yoyossef/ds360.git 42 | cd ds360 43 | make install 44 | ``` 45 | 46 | ## How to uninstall 47 | 48 | ``` 49 | make uninstall 50 | ``` 51 | 52 | ## Troubleshooting 53 | 54 | The script should work fine if the Dualsense controller is connected through USB or via Bluetooth, but if you still get the "VID not found" error, please check your `/proc/bus/input/devices` file and see if your controller is there. 55 | 56 | If it's not recheck if your kernel includes the `hid-playstation` driver and try to use `xboxdrv` manually with `dualsense.xboxdrv` config (edit the commented `evdev=...` line). 57 | 58 | 59 | Hint: you can find the value to use for evdev with this command (thanks to /u/QushAes) 60 | ```sh 61 | for sysdevpath in $(find /sys/bus/usb/devices/usb*/ -name dev); do 62 | ( 63 | syspath="${sysdevpath%/dev}" 64 | devname="$(udevadm info -q name -p $syspath)" 65 | [[ "$devname" == "bus/"* ]] && exit 66 | eval "$(udevadm info -q property --export -p $syspath)" 67 | [[ -z "$ID_SERIAL" ]] && exit 68 | echo "/dev/$devname - $ID_SERIAL" 69 | ) 70 | done 71 | ``` 72 | -------------------------------------------------------------------------------- /ds360.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | int execvp(const char *file, const char *const (&argv)[N]) 11 | { 12 | assert((N > 0) && (argv[N - 1] == nullptr)); 13 | 14 | return execvp(file, const_cast(argv)); 15 | } 16 | 17 | struct VIDException : public std::exception 18 | { 19 | const char *what() const throw() 20 | { 21 | return "VID not found in /proc/bus/input/devices, please make sure your Dualsense is connected"; 22 | } 23 | }; 24 | 25 | std::string get_device(std::string device_vid, std::string device_pid) 26 | { 27 | std::ifstream file_input; 28 | std::size_t pos; 29 | std::string device_path, current_line, search_str, event_str; 30 | std::string device_list_file = "/proc/bus/input/devices"; 31 | bool vid_pid_found = false; 32 | bool debug = false; 33 | 34 | // 1. open device list file 35 | file_input.open(device_list_file.c_str()); 36 | if (!file_input.is_open()) 37 | { 38 | std::cerr << "file_input.open >> " << std::strerror(errno) << std::endl; 39 | throw -2; 40 | } 41 | 42 | // 2. search for first VID:PID and get event number 43 | search_str = "Vendor=" + device_vid + " Product=" + device_pid; 44 | while (getline(file_input, current_line)) 45 | { 46 | if (!vid_pid_found) 47 | { 48 | pos = current_line.find(search_str, 0); 49 | if (pos != std::string::npos) 50 | { 51 | vid_pid_found = true; 52 | search_str = "event"; 53 | } 54 | } 55 | else 56 | { 57 | pos = current_line.find(search_str, 0); 58 | if (pos != std::string::npos) 59 | { 60 | event_str = current_line.substr(pos); 61 | // find space and substring event## 62 | pos = event_str.find(' ', 0); 63 | event_str = event_str.substr(0, pos); 64 | break; 65 | } 66 | } 67 | } 68 | if (!vid_pid_found) 69 | throw VIDException(); 70 | 71 | // 3. build device path 72 | device_path = "evdev=/dev/input/" + event_str; 73 | if (debug) 74 | std::cout << device_path << std::endl; 75 | 76 | return device_path; 77 | } 78 | 79 | void create_pid_file() { 80 | std::string pid_file = "/tmp/ds360.pid"; 81 | pid_t pid = getpid(); 82 | std::ofstream file_output; 83 | auto pid_str = std::to_string(pid); 84 | 85 | std::cout << "pid: " << pid_str << std::endl; 86 | file_output.open(pid_file.c_str()); 87 | 88 | if (!file_output.is_open()) 89 | { 90 | std::cerr << "file_output.open >> " << std::strerror(errno) << std::endl; 91 | throw -2; 92 | } 93 | 94 | file_output << pid_str.c_str() << std::endl; 95 | file_output.close(); 96 | } 97 | 98 | int main(int argc, char* argv[]) 99 | { 100 | try 101 | { 102 | create_pid_file(); 103 | std::string event_path = get_device("054c", "0ce6"); 104 | if (argc < 2) { 105 | execvp("xboxdrv", 106 | {"xboxdrv", 107 | "-o", event_path.c_str(), 108 | "--mimic-xpad", 109 | "--silent", 110 | "--quiet", 111 | "--axismap", 112 | "-y1=y1,-y2=y2", 113 | "--evdev-absmap", 114 | "ABS_HAT0X=dpad_x,ABS_HAT0Y=dpad_y,ABS_X=X1,ABS_Y=Y1,ABS_RX=X2,ABS_RY=Y2,ABS_Z=LT,ABS_RZ=RT", 115 | "--evdev-keymap", 116 | "BTN_SOUTH=A,BTN_EAST=B,BTN_NORTH=Y,BTN_WEST=X,BTN_START=start,BTN_MODE=guide,BTN_SELECT=back,BTN_TL=LB,BTN_TR=RB,BTN_TL2=LT,BTN_TR2=RT,BTN_THUMBL=TL,BTN_THUMBR=TR", 117 | nullptr} 118 | ); 119 | } 120 | else { 121 | execvp("xboxdrv", {"xboxdrv", "-c", argv[1], "-o", event_path.c_str(), nullptr}); 122 | } 123 | } 124 | catch (const std::exception &e) 125 | { 126 | std::cerr << e.what() << '\n'; 127 | return 1; 128 | } 129 | return 0; 130 | } --------------------------------------------------------------------------------