Skip to content

Conversation

@chenx-dust
Copy link

Method

Steam Controller with Handheld Mode uses uhid to emulate controller, and there is an UHID_SET_REPORT event that carrying a steam setting named SETTING_STABILIZER_ENABLED, which signals whether gyro is using. I capture this event and trigger an IMU toggle callback.

Unfortunately, I only found Steam Controller with Handheld Mode has this kind of action, so it will not work at Dualsense or other modes.

It is a Proof of Concept, and ONLY tested on my GPD Win Mini 2024.

Comparison

Here is an experiment on my GPD Win Mini 2024 with few apps in background and only 2 cores enabled. Checking power consumption and CPU usage through btop.

With Enable Gyro on Demand

w

Power: 7.85W, CPU Usage: 1.5% (HHD only)

Without Enable Gyro on Demand

wo

Power: 9.69W, CPU Usage: 4.1% (HHD) + 4.6% (IRQ from IMU)

Tested on GPD Win Mini 2024 (R7-8840U)

@antheas
Copy link
Contributor

antheas commented Sep 11, 2025

Since the controller is cached between runs, can you just make it crash the controller and when it restarts disable gyro in get_outputs()?

@antheas
Copy link
Contributor

antheas commented Sep 11, 2025

this is so you do not touch every device driver. You can keep a bool in the handheld controller so you can check it in get_outputs to see if you should enable gyro

@chenx-dust
Copy link
Author

Since the controller is cached between runs, can you just make it crash the controller and when it restarts disable gyro in get_outputs()?

I got it. It's really simpler but I still meet some problems:

  • I cannot read cached controller in get_outputs(). The cache is loaded in open(), which is too late for get_outputs()
  • Then I turned to use global variable. It worked. And I crashed it when gyro active status changes.
  • However, there is an ERROR_DELAY for driver to restart. It's like kind of glitch, making experience bad during restart. This become a significant issue when calibrating gyro, because the controller stick on last state and make it impossible to percisely select the "Calibrate" tab. If I want to change, I have to edit every device driver. :(

Do you have any good idea?

@antheas
Copy link
Contributor

antheas commented Sep 11, 2025

Since the controller is cached between runs, can you just make it crash the controller and when it restarts disable gyro in get_outputs()?

I got it. It's really simpler but I still meet some problems:

  • I cannot read cached controller in get_outputs(). The cache is loaded in open(), which is too late for get_outputs()

Add a function to SteamdeckController that works like .close_cached() and return self.cached and self.cached.gyro_enabled where gyro_enabled defaults to true.

Dualsense.close_cached()
SteamdeckController.close_cached()
version = 1
sync_gyro = False
paddles_as = conf.get("uinput.paddles_as", "noob")
if controller == "joycon_pair":
theme = "joycon_pair"
nintendo_qam = conf["joycon_pair.nintendo_qam"].to(bool)
button_map = GAMEPAD_BUTTON_MAP
bus = 0x06
version = 0
elif controller == "hori_steam":
theme = "hori_steam"
noob_mode = conf.get("hori_steam.noob_mode", False)

  • Then I turned to use global variable. It worked. And I crashed it when gyro active status changes.

See above.

  • However, there is an ERROR_DELAY for driver to restart. It's like kind of glitch, making experience bad during restart. This become a significant issue when calibrating gyro, because the controller stick on last state and make it impossible to percisely select the "Calibrate" tab. If I want to change, I have to edit every device driver. :(

Make the option be always on. After you make the changes, I will give it a go and see. I kind of see what you mean.

@antheas
Copy link
Contributor

antheas commented Sep 11, 2025

You are right... restarting the controller takes too long

@antheas
Copy link
Contributor

antheas commented Sep 11, 2025

We need to make sure we change as few lines as possible on the common code parts, because its always dangerous

@chenx-dust
Copy link
Author

We need to make sure we change as few lines as possible on the common code parts, because its always dangerous

I agree.

@antheas
Copy link
Contributor

antheas commented Sep 11, 2025

Instead of disabling the imu, make in_accel_sampling_frequency and in_angvel_sampling_frequency take their minimum value which they cache at the beginning, and if there is a trigger, update that as well.

Because I am afraid the fast enable and disable are just going to break the kernel

@chenx-dust
Copy link
Author

Instead of disabling the imu, make in_accel_sampling_frequency and in_angvel_sampling_frequency take their minimum value which they cache at the beginning, and if there is a trigger, update that as well.

Because I am afraid the fast enable and disable are just going to break the kernel

That's reasonable. I'll do that tomorrow, because it's midnight at my time zone. :)

@chenx-dust
Copy link
Author

chenx-dust commented Sep 12, 2025

I've taken a look into it. Lowest sampling frequency may be still pretty high.
Let's take BMI160 as an example. The data sheet shows that its lowest output data rate is 25Hz. The CPU usage in such sampling rate still cannot be ignored.

Apart from that, I've tested that open and close IMU device frequently is actually OK. Also, we may adopt debounce to avoid too high frequency.

Supplementary Real output:

$ cat /sys/bus/iio/devices/iio:device0/in_accel_sampling_frequency_available 
0.78125 1.5625 3.125 6.25 12.5 25 50 100 200 400 800 1600
$ cat /sys/bus/iio/devices/iio:device0/in_anglvel_sampling_frequency_available
25 50 100 200 400 800 1600 3200

@chenx-dust
Copy link
Author

I still recommend the original solution. Even if it cause something broken, there's an toggle to turn off this feature.

@chenx-dust
Copy link
Author

chenx-dust commented Sep 13, 2025

BTW, I found that QAM shortcut (Xbox+A) doesn't work in Steam Controller with Handheld Mode. Is there any reason?

@antheas
Copy link
Contributor

antheas commented Sep 13, 2025

Check this out. I did it two days ago I think. It does not work well.

But I think the approach of passing an event through from the controller to the imu has merit. It decreases the change to the driver code to two lines and does not require doing the crash.

I tried changing the frequency, and that worked very badly. So that's where you can help

https://github.com/hhd-dev/hhd/tree/sync-attempt

@chenx-dust
Copy link
Author

But I think the approach of passing an event through from the controller to the imu has merit. It decreases the change to the driver code to two lines and does not require doing the crash.

Sounds great! I took a deeper look into HHD, and now I have a better understanding of its event logic. I'll go ahead.

@chenx-dust
Copy link
Author

As far as I know, the IMU device, and even the whole controller loop, doesn't act as an event consumer, so the common event cannot be passed into it.
I consider to use a config value to trigger controller reload.

@chenx-dust
Copy link
Author

chenx-dust commented Sep 14, 2025

I'm sure you will be satisfied with this method. :)
Really clean and safe.

@antheas
Copy link
Contributor

antheas commented Sep 14, 2025

As far as I know, the IMU device, and even the whole controller loop, doesn't act as an event consumer, so the common event cannot be passed into it.
I consider to use a config value to trigger controller reload.

Yes we need to add 2 lines for imu to consume. But that's ok.

I tested it and the event passthrough works. But turning down the frequency didnt

@chenx-dust
Copy link
Author

Yes we need to add 2 lines for imu to consume. But that's ok.

I tested it and the event passthrough works. But turning down the frequency didnt

I've tested that completely restarting controller takes about 0.1s, which is totally acceptable. IMO, throwing IMU into event loop is kind of ... complicated?

In my latest commit, I continue using the restart method, but trigger it by making change on config. I think it's simpler and more elegant. Please check it out.

@antheas
Copy link
Contributor

antheas commented Sep 14, 2025

I was testing restarts and on the Ayaneo 3 it takes a bit longer which ends up freezing input so it's not possible to navigate the calibration panel

@chenx-dust
Copy link
Author

OK, I'll switch to event method.

@chenx-dust
Copy link
Author

Emm, here I'm facing a new problem. The controller fds is not "Plug and Play". They are determined in the initialization process. So if I want to disconnect a device, its fd will be invalid and make the controller crash. Adding some code to adjust fds list is quite dirty (, which is similar to the first version of my code). The better way may be to refactor the fds initialization logics, and make it fetch from every device in every loop. But it does a lot more changes in controller code.

@antheas
Copy link
Contributor

antheas commented Sep 14, 2025

The controller is always at least 25hz. This is so consumers don't break while reading the controller and for deferred events to fire correctly.

So I don't see a need to turn off the gyro completely if it's easier

@antheas
Copy link
Contributor

antheas commented Sep 14, 2025

5-10 Hz shouldn't affect utilization much

@chenx-dust
Copy link
Author

Oh, it seems that I misunderstood your words.

I tested it and the event passthrough works. But turning down the frequency didnt

I thought you meant that turning down the frequency worked bad so won't use it. Just code doesn't work well? Or any other problem?

Apart from that, I still have some hope to use the method that completely turns off IMU. Because it performs best, while a little latency doesn't affect much. There's many other way to get into calibrate panel, like touchscreen or even turning off the on-demand feature. And most of the user will meet this latency only when they open a gyro-enabled game, where they still need to wait for the game loading, and no gamepad action is needed.

@antheas
Copy link
Contributor

antheas commented Sep 14, 2025

Sorry for the ambiguity. Yes, my code did not work well. If it's possible it would be great. Could even go down to 2hz. The only nonideal part is that the imu stops being available for screen rotation

@chenx-dust
Copy link
Author

I removed all buffer/enable related writing code in power save logic, and it just worked, although I don't know why.

Apart from that, I made it default to enable IMU power save on start, and disable gyro sync when IMU is in power save mode.

@chenx-dust
Copy link
Author

Kindly remind this pr.

@antheas
Copy link
Contributor

antheas commented Sep 21, 2025

I am very busy currently. Let me circle back in a few days. Feel free to remind me if i don't

@chenx-dust
Copy link
Author

Maybe we can push this pr :)

@antheas
Copy link
Contributor

antheas commented Nov 8, 2025

Now that I versioned V4, I am about to do a lot of breaking changes so it is a good opportunity to.

I also made it so Bazzite testing builds using the master branch so these changes can be tested without affecting broader users.

Can you rebase it?

@chenx-dust
Copy link
Author

It seems that there is no conflict in it. Fast forward is ok.

@dkatzdev
Copy link
Contributor

dkatzdev commented Dec 9, 2025

this will be nice as I never use gyro lol

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