diff --git a/README.md b/README.md index ce39b08..9400657 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Thus far, ```vgamepad``` is compatible with Windows only. - [Getting started](#getting-started) - [XBox360 gamepad](#xbox360-gamepad) - [DualShock4 gamepad](#dualshock4-gamepad) + - [Rumble and LEDs](#rumble-and-leds) - [Contribute](#authors) --- @@ -326,41 +327,51 @@ gamepad.update() time.sleep(1.0) ``` -### Receive state changes - -To receive LED ring changes and rumble/vibration requests, you need to define your own callback function, then call `gamepad.register_notification(your_callback)`. If nothing is supplied, it will simply print out all state changes ([int 0-255]LargeMortor, SmallMortor and LedNumber). +--- -Your callback function need to have 6 parameters: `client, target, LargeMotor, SmallMotor, LedNumber, UserData`. For more information, see [sdk/include/ViGEm/Client.h](https://github.com/ViGEm/ViGEmBus/blob/442ae3b85693b48866d0627af6f485f918b08d03/sdk/include/ViGEm/Client.h). +### Rumble and LEDs: -To unregister, call `gampad.unregister_notification()` +`vgamepad` enables registering custom callback function to handle updates of the rumble motors, and of the LED ring. -Example: +The custom callback function requires 6 parameters: +```python +def my_callback(client, target, large_motor, small_motor, led_number, user_data): + """ + Callback function triggered at each received state change + + :param client: vigem bus ID + :param target: vigem device ID + :param large_motor: integer in [0, 255] representing the state of the large motor + :param small_motor: integer in [0, 255] representing the state of the small motor + :param led_number: integer in [0, 255] representing the state of the LED ring + :param user_data: placeholder, do not use + """ + # Do your things here. For instance: + print(f"Received notification for client {client}, target {target}") + print(f"large motor: {large_motor}, small motor: {small_motor}") + print(f"led number: {led_number}") +``` +The callback function needs to be registered as follows: ```python -import vgamepad as vg -gamepad = vg.VX360Gamepad() +gamepad.register_notification(callback_function=my_callback) +``` -def example_callback(client, target, LargeMotor, SmallMotor, LedNumber, UserData): - #Do your things here, change LED light, power a motor, or just return these value. - pass -gamepad.register_notification() -#When state changes, callbacks are made, default callback function will print out like this: +Each time the state of the gamepad is changed (for example by a video game that sends rumbling requests), the callback function will then be called. + +In our example, when state changes are received, something like the following will be printed to `stdout`: +```terminal +Received notification for client 2876897124288, target 2876931874736 +large motor: 255, small motor: 255 +led number: 0 +Received notification for client 2876897124288, target 2876931874736 +large motor: 0, small motor: 0 +led number: 0 ``` -```bash -LargeMotor: 64, SmallMotor: 30, LedNumber: 0 -LargeMotor: 255, SmallMotor: 255, LedNumber: 0 -LargeMotor: 67, SmallMotor: 32, LedNumber: 0 -LargeMotor: 65, SmallMotor: 32, LedNumber: 0 -LargeMotor: 64, SmallMotor: 32, LedNumber: 0 -LargeMotor: 63, SmallMotor: 31, LedNumber: 0 -LargeMotor: 63, SmallMotor: 81, LedNumber: 0 -LargeMotor: 127, SmallMotor: 127, LedNumber: 0 -LargeMotor: 16, SmallMotor: 83, LedNumber: 0 -LargeMotor: 5, SmallMotor: 28, LedNumber: 0 -LargeMotor: 0, SmallMotor: 19, LedNumber: 0 -LargeMotor: 0, SmallMotor: 57, LedNumber: 0 -LargeMotor: 0, SmallMotor: 51, LedNumber: 0 +If not needed anymore, the callback function can be unregistered: +```python +gamepad.unregister_notification() ``` --- diff --git a/setup.py b/setup.py index 04ea7b1..20e4b24 100644 --- a/setup.py +++ b/setup.py @@ -34,14 +34,14 @@ setup( name='vgamepad', packages=[package for package in find_packages()], - version='0.0.5', + version='0.0.6', license='MIT', description='Virtual XBox360 and DualShock4 gamepads in python', long_description=long_description, long_description_content_type="text/markdown", author='Yann Bouteiller', url='https://github.com/yannbouteiller/vgamepad', - download_url='https://github.com/yannbouteiller/vgamepad/archive/refs/tags/v0.0.5.tar.gz', + download_url='https://github.com/yannbouteiller/vgamepad/archive/refs/tags/v0.0.6.tar.gz', keywords=['virtual', 'gamepad', 'python', 'xbox', 'dualshock', 'controller', 'emulator'], install_requires=[], classifiers=[ diff --git a/vgamepad/win/vigem_client.py b/vgamepad/win/vigem_client.py index 4d9b98f..9066150 100644 --- a/vgamepad/win/vigem_client.py +++ b/vgamepad/win/vigem_client.py @@ -226,7 +226,6 @@ vigem_target_x360_get_user_index.argtypes = (c_void_p, c_void_p, c_void_p) vigem_target_x360_get_user_index.restype = c_uint -# TODO: add the missing APIs (those with C callback functions) """ Registers a function which gets called, when LED index or vibration state changes occur on the provided target device. This function fails if the provided @@ -246,7 +245,7 @@ @param target The target device object. """ vigem_target_x360_unregister_notification = vigemClient.vigem_target_x360_unregister_notification -vigem_target_x360_unregister_notification.argtypes = (c_void_p) +vigem_target_x360_unregister_notification.argtypes = (c_void_p, ) vigem_target_x360_unregister_notification.restype = None """ @@ -268,15 +267,5 @@ @param target The target device object. """ vigem_target_ds4_unregister_notification = vigemClient.vigem_target_ds4_unregister_notification -vigem_target_ds4_unregister_notification.argtypes = (c_void_p) +vigem_target_ds4_unregister_notification.argtypes = (c_void_p, ) vigem_target_ds4_unregister_notification.restype = None - - - - - - - - - -# diff --git a/vgamepad/win/virtual_gamepad.py b/vgamepad/win/virtual_gamepad.py index 92bf290..e0d337c 100644 --- a/vgamepad/win/virtual_gamepad.py +++ b/vgamepad/win/virtual_gamepad.py @@ -7,14 +7,27 @@ import ctypes from ctypes import CFUNCTYPE, c_void_p, c_ubyte from abc import ABC, abstractmethod -from inspect import signature #Check if user defined callback function is legal +from inspect import signature # Check if user defined callback function is legal + def check_err(err): if err != vcom.VIGEM_ERRORS.VIGEM_ERROR_NONE: raise Exception(vcom.VIGEM_ERRORS(err).name) -def defaultCallback(client, target, LargeMotor, SmallMotor, LedNumber, UserData): - print("LargeMotor: {lm}, SmallMotor: {sm}, LedNumber: {ln}".format(lm = LargeMotor, sm = SmallMotor, ln = LedNumber)) + +def dummy_callback(client, target, large_motor, small_motor, led_number, user_data): + """ + Pattern for callback functions to be registered as notifications + + :param client: vigem bus ID + :param target: vigem device ID + :param large_motor: integer in [0, 255] representing the state of the large motor + :param small_motor: integer in [0, 255] representing the state of the small motor + :param led_number: integer in [0, 255] representing the state of the LED ring + :param user_data: placeholder, do not use + """ + pass + class VBus: """ @@ -42,6 +55,7 @@ def __init__(self): self._busp = self.vbus.get_busp() self._devicep = self.target_alloc() self.CMPFUNC = CFUNCTYPE(None, c_void_p, c_void_p, c_ubyte, c_ubyte, c_ubyte, c_void_p) + self.cmp_func = None vcli.vigem_target_add(self._busp, self._devicep) assert vcli.vigem_target_is_attached(self._devicep), "The virtual device could not connect to ViGEmBus." @@ -208,18 +222,26 @@ def update(self): """ check_err(vcli.vigem_target_x360_update(self._busp, self._devicep, self.report)) - def target_alloc(self): - return vcli.vigem_target_x360_alloc() + def register_notification(self, callback_function): + """ + Registers a callback function that can handle force feedback, leds, etc. - def register_notification(self, callback_func = defaultCallback): - if not signature(callback_func) == signature(defaultCallback): - raise TypeError("Needed callback function signature: {need}, but got: {got}".format(need = signature(defaultCallback), got = signature(callback_func))) - self.cmp_func = self.CMPFUNC(callback_func) #keep its reference, otherwise programe will crash when a callback is made. + :param: a function of the form: my_func(client, target, large_motor, small_motor, led_number, user_data) + """ + if not signature(callback_function) == signature(dummy_callback): + raise TypeError("Needed callback function signature: {}, but got: {}".format(signature(dummy_callback), signature(callback_function))) + self.cmp_func = self.CMPFUNC(callback_function) # keep its reference, otherwise the program will crash when a callback is made. check_err(vcli.vigem_target_x360_register_notification(self._busp, self._devicep, self.cmp_func, None)) def unregister_notification(self): + """ + Unregisters a previously registered callback function. + """ vcli.vigem_target_x360_unregister_notification(self._devicep) + def target_alloc(self): + return vcli.vigem_target_x360_alloc() + class VDS4Gamepad(VGamepad): """ @@ -380,14 +402,22 @@ def update_extended_report(self, extended_report): """ check_err(vcli.vigem_target_ds4_update_ex_ptr(self._busp, self._devicep, ctypes.byref(extended_report))) - def target_alloc(self): - return vcli.vigem_target_ds4_alloc() + def register_notification(self, callback_function): + """ + Registers a callback function that can handle force feedback, leds, etc. - def register_callback(self, callback_func = defaultCallback): - if not signature(callback_func) == signature(defaultCallback): - raise TypeError("Needed callback function signature: {need}, but got: {got}".format(need = signature(defaultCallback), got = signature(callback_func))) - self.cmp_func = self.CMPFUNC(callback_func) + :param: a function of the form: my_func(client, target, large_motor, small_motor, led_number, user_data) + """ + if not signature(callback_function) == signature(dummy_callback): + raise TypeError("Needed callback function signature: {}, but got: {}".format(signature(dummy_callback), signature(callback_function))) + self.cmp_func = self.CMPFUNC(callback_function) check_err(vcli.vigem_target_ds4_register_notification(self._busp, self._devicep, self.cmp_func, None)) def unregister_notification(self): + """ + Unregisters a previously registered callback function. + """ vcli.vigem_target_ds4_unregister_notification(self._devicep) + + def target_alloc(self): + return vcli.vigem_target_ds4_alloc()