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

Add option to change pedal axis behavior #110

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

Lars-Saetaberget
Copy link

Overview

This change allows users to change how their pedal axes behave, such as inverting the axes, making them start out centered etc.

This is useful in certain games where the axes are not treated properly, and there is no option to change it in-game, such as in Trackmania. Some of the modes might seem quite strange, but I added some of these with combined pedals in mind, even though I don't have much experience with flying games, so I don't know how useful they will be.

Injector device

Ideally this should probably be done on an individual driver level, and I started by looking into doing this in new-lg4ff, but doing it in oversteer directly does allow us to create this functionality even for devices that do not have a built-in option for it. This should also allow us to do other things such as enable combined pedal support for devices with no option to combine pedals. In the future, checks could be implemented to see if the driver supports it natively, and if not, then we use the injector device method.

There are a couple of negatives to doing this in oversteer however:

  1. It requires (to my knowledge) that we create a secondary "injector device" that we can inject events into. This means that there will be another device in-game that you have to select in order to use this feature (the device will not appear if using normal axis behavior however, so existing keybinds etc. should be unaffected)
  2. Oversteer must be kept open while playing to make use of this new functionality, since the injector device lives in oversteer memory
  3. This is a pretty minor point, but creating the injector device the first time you select something other than normal freezes the UI for about a second. Could maybe do the injector creation in a background task to prevent this though.

Input needed

I created this PR as a draft as it is close, but not yet fully complete, and I require some input and guidance to put the last finishing touches on this:

  1. Devices with ranges other than 0-255: This should be simple to implement, but I have no devices other than my G923 to test with. I was also a bit surprised at some of the devices and was wondering if I had misunderstood something; Is it really the case that wid.LG_WFG and wid.LG_WFFG only have 4 different values for their pedals for instance? (This is judging from what happens in device.py:normalize_event() )
  2. UI: I am very much not a frontend guy, and I also use i3wm, so what looks fine for me might look really wacky for others. Would appreciate if you could take a look at the changes for this.
  3. argparse: I have not created an option for setting the axis mode, but I could do that before finishing the PR
  4. test: I have not taken a look at the testing framework at all. Should we implement some tests for this feature?

Other stuff

Any refactoring, logic changes etc. that do not fall into any of the above categories are of course also welcome.

Thanks for reading my wall of text!

This change allows users to change how their pedal axes behave.
Such as inverting the axes, making them start out centered etc.
@Lars-Saetaberget
Copy link
Author

One last thing: There is also a visual bug when you combine pedals, where sometimes the indicator for the combined pedal (brakes or clutch) will become stuck in a non-neutral position when depressing the accelerator. This seems to be because new-lg4ff sends an event for the combined pedal when pressing the accelerator for the first time, and I don't know if this is really fixable from this end.

@berarma
Copy link
Owner

berarma commented Sep 11, 2022

Thanks for the contribution! It looks good.

I've been thinking to use UInput for other reasons. Mainly to create a virtual device that combined several ones: wheel, pedals, shifter, handbrake, button pads, etc. And to a lesser extent, to move some new-lg4ff functionality here like you said, that way more drivers would be supported.

I didn't use it because combining several devices would require many changes to the UI, thus a lot of work to make it simple. I'm also worried about device detection since the real device can't be hidden and the virtual device might not be correctly identified. I might use this as a starting point to test virtual devices. Have you done some testing in games/Proton?

I was also a bit surprised at some of the devices and was wondering if I had misunderstood something; Is it really the case that wid.LG_WFG and wid.LG_WFFG only have 4 different values for their pedals for instance? (This is judging from what happens in device.py:normalize_event() )

You might be talking about:

event.value = event.value * 64

Here I'm converting the X axis from 10bits to 16bits. AFAIK it's not an 8bit pedal value.

Don't worry about the UI. I'm revising it anyway when new elements are added so it's held compact.

Sadly, I don't have any tests or testing framework. It should be my job to start writing tests for the most basic things.

Are all those pedal modes really needed or is it just because we can? I know it's easy to implement but won't they make the UI more hard to understand? Are we implementing the things people really might need and expect?

I'm not sure how this new feature will interact with "combine pedals". Should we merge them?

One last thing: There is also a visual bug when you combine pedals, where sometimes the indicator for the combined pedal (brakes or clutch) will become stuck in a non-neutral position when depressing the accelerator. This seems to be because new-lg4ff sends an event for the combined pedal when pressing the accelerator for the first time, and I don't know if this is really fixable from this end.

I see the brakes pedal gets sometimes stuck at 50% while most times it's at 0% when switching to combine gas/brakes. Is that what you mean? I'll investigate. This might happen in the mainline kernel module too.

@Lars-Saetaberget
Copy link
Author

I'm also worried about device detection since the real device can't be hidden and the virtual device might not be correctly identified.

A fair concern. Are you thinking from the user side or the games themselves? I imagine most games would display both, and you would then probably need to unbind at least the pedals from your real device in-game. From the user side it might be confusing with lots of features that use this UInput method.

Maybe an explicit toggle of the virtual device in the UI is the key? That way the user will (hopefully) be aware that a virtual device is active, and then we can just grey out the unavailable options in the UI if a virtual device is required, but not enabled.

Have you done some testing in games/Proton?

I have tested in-game through wine so I think it should work, but not specifically proton just yet. I will make sure to get some and try them out to be sure though. I'm fairly new to racing games, so I don't have much of a library at the moment.

Are all those pedal modes really needed or is it just because we can? I know it's easy to implement but won't they make the UI more hard to understand? Are we implementing the things people really might need and expect?

Very possible that some of these are not needed, but what people think they need and what they actually need are not always one and the same. I initially thought I needed to simply invert the axis to get TM working for example, but what I actually needed is what's currently known as Centered Half Inverted.

The looping and switching ones would probably be my first candidates to remove, as they don't really make sense without combined pedals, and I don't know if incorrect axes is a realistic issue in those types of games.

I'm not sure how this new feature will interact with "combine pedals". Should we merge them?

I'm not sure exactly what you mean by this? Currently this setup works with combine pedals for me in that if you combine clutch and accelerate and select Inverted for example, the pedals will indeed be combined, but they move the acceleration axis in the opposite direction of what they normally would. Note however that this is strictly based on what I'm seeing in oversteer and outgoing events, and is not verified in-game, as I have no such games that I'm aware of.

We could include combined pedals in the virtual device of course if that's what you meant.

Sadly, I don't have any tests or testing framework.

Ah, I see. I just saw a bunch of references to test.py and such, and I assumed it was automated testing. But now I see that it's about testing force feedback etc.

I see the brakes pedal gets sometimes stuck at 50% while most times it's at 0% when switching to combine gas/brakes. Is that what you mean? I'll investigate. This might happen in the mainline kernel module too.

Yep, that's it! No big deal I should think, and it's very possible that it comes from the upstream module like you said.

Thanks for the feedback! I'll do some more testing and see if I can support more ranges than 0-255 for now.

@berarma
Copy link
Owner

berarma commented Sep 12, 2022

I'm also worried about device detection since the real device can't be hidden and the virtual device might not be correctly identified.

A fair concern. Are you thinking from the user side or the games themselves? I imagine most games would display both, and you would then probably need to unbind at least the pedals from your real device in-game. From the user side it might be confusing with lots of features that use this UInput method.

Maybe an explicit toggle of the virtual device in the UI is the key? That way the user will (hopefully) be aware that a virtual device is active, and then we can just grey out the unavailable options in the UI if a virtual device is required, but not enabled.

Some games detect your wheel and automatically apply a given settings profile. Some may list only devices detected as wheels or that resemble wheels. I wonder how those games might behave when presented with a wheel device they can't use and a "custom" device.

Thanks for the feedback! I'll do some more testing and see if I can support more ranges than 0-255 for now.

What do you mean? The steering wheel axis uses a different number of bits depending on the device, but pedals seem to use always only 8 bits, at least for the currently supported devices.

@albfan
Copy link
Contributor

albfan commented Mar 18, 2023

thrustmaster T3PA goes in range 0-1023, so I can test that part.

Still I think this needs #131, will try to get in sync with this and put changes to deal with different vendors on top of this

@berarma
Copy link
Owner

berarma commented Mar 18, 2023

thrustmaster T3PA goes in range 0-1023, so I can test that part.

Still I think this needs #131, will try to get in sync with this and put changes to deal with different vendors on top of this

Different vendors are already dealt with in the master branch. This PR creates a virtual device that works as a modified real device. This isn't needed for #131.

@albfan
Copy link
Contributor

albfan commented Mar 19, 2023

I check patch and is mostly working. @Lars-Saetaberget you forget to process data all the time:

diff --git i/oversteer/device.py w/oversteer/device.py
index b7319ae..fd402dd 100644
--- i/oversteer/device.py
+++ w/oversteer/device.py
@@ -434,6 +434,6 @@ class Device:
                     event.code = ecodes.ABS_RZ
                 elif event.code == ecodes.ABS_THROTTLE:
                     event.code = ecodes.ABS_Y
-            elif self.pedal_mode != AxisMode.NORMAL and event.code in [ecodes.ABS_RZ, ecodes.ABS_Y, ecodes.ABS_Z]:
+            if self.pedal_mode != AxisMode.NORMAL and event.code in [ecodes.ABS_RZ, ecodes.ABS_Y, ecodes.ABS_Z]:
                 event.value = get_modified_value(self.pedal_mode, event.value)
         return event

and for different values we just need to adjust the value depending on vendor (I guess):

diff --git i/oversteer/pedal_mode.py w/oversteer/pedal_mode.py
index 189d421..b4933cc 100644
--- i/oversteer/pedal_mode.py
+++ w/oversteer/pedal_mode.py
@@ -4,6 +4,7 @@ from enum import Enum
 
 ABS_mode = namedtuple("ABS_mode", ["id", "name"])
 
+ABS_MAX = 1024
 
 class AxisMode(Enum):
     NORMAL                      = ABS_mode(0, "Normal")
@@ -25,45 +26,46 @@ class CombinedPedals:
 
 
 def get_modified_value(mode, value):
+    print(value)
     if mode == AxisMode.NORMAL:
         return value
 
     elif mode == AxisMode.INVERTED:
-        return 255 - value
+        return ABS_MAX - value
 
     elif mode == AxisMode.HALF:
-        return value + int((255 - value + 1) / 2)
+        return value + int((ABS_MAX - value + 1) / 2)
 
     elif mode == AxisMode.HALF_INVERTED:
-        return (int(value / 2) - int(255 / 2)) * -1
+        return (int(value / 2) - int(ABS_MAX / 2)) * -1
 
     elif mode == AxisMode.CENTERED_HALF:
         return int(value / 2)
 
     elif mode == AxisMode.CENTERED_HALF_INVERTED:
-        return 255 - int(value / 2)
+        return ABS_MAX - int(value / 2)
 
     elif mode == AxisMode.CENTERED_SWITCHING:
-        value = value - int(255 / 2) - 1
+        value = value - int(ABS_MAX / 2) - 1
         if value < 0:
-            value = (value * -1) + int(255 / 2)
+            value = (value * -1) + int(ABS_MAX / 2)
         return value
 
     elif mode == AxisMode.CENTERED_SWITCHING_INVERTED:
-        if value > int(255 / 2):
-            value = int(255 + 255 / 2) - value + 1
+        if value > int(ABS_MAX / 2):
+            value = int(ABS_MAX + ABS_MAX / 2) - value + 1
         return value
 
     elif mode == AxisMode.CENTERED_LOOPING:
-        value = value - int(255 / 2) - 1
+        value = value - int(ABS_MAX / 2) - 1
         if value < 0:
-            value += 256
+            value += ABS_MAX
         return value
 
     elif mode == AxisMode.CENTERED_LOOPING_INVERTED:
-        value = int(255 / 2) - value
+        value = int(ABS_MAX / 2) - value
         if value < 0:
-            value += 256
+            value += ABS_MAX
         return value
 
     return value

in any case, pedal_mode is a mock for what driver should provide right? there's no wrapper function in oversteer that changes the final input, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants