-
-
Notifications
You must be signed in to change notification settings - Fork 204
feat: Gyro mouse aiming #875
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
Closed
unbelievableflavour
wants to merge
1
commit into
utkarshdalal:master
from
unbelievableflavour:gyro-aiming
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
app/src/main/java/com/winlator/widget/GyroAimingHelper.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| package com.winlator.widget; | ||
|
|
||
| import android.content.Context; | ||
| import android.hardware.Sensor; | ||
| import android.hardware.SensorEvent; | ||
| import android.hardware.SensorEventListener; | ||
| import android.hardware.SensorManager; | ||
| import android.hardware.display.DisplayManager; | ||
| import android.os.Handler; | ||
| import android.os.Looper; | ||
| import android.view.Display; | ||
| import android.view.Surface; | ||
|
|
||
| import timber.log.Timber; | ||
|
|
||
| /** | ||
| * Listens to the device gyroscope and converts angular velocity to relative mouse deltas | ||
| * for aiming. Only active when started; use from the main thread. | ||
| */ | ||
| public class GyroAimingHelper implements SensorEventListener { | ||
| public interface Listener { | ||
| void onMouseDelta(int dx, int dy); | ||
| } | ||
|
|
||
| private static final float DEFAULT_SENSITIVITY = 400f; | ||
| private static final float MAX_DELTA_PER_FRAME = 120f; | ||
| private static final int SENSOR_DELAY_US = 5_000; // 200 Hz | ||
|
|
||
| private final Context context; | ||
| private final Listener listener; | ||
| private final Handler mainHandler; | ||
| private float sensitivity; | ||
|
|
||
| private SensorManager sensorManager; | ||
| private DisplayManager displayManager; | ||
| private Sensor gyro; | ||
| private boolean running; | ||
| private long lastTimestampNs = 0; | ||
| private boolean hasLastTimestamp; | ||
| /** Sub-pixel accumulation so small movements aren't lost when casting to int */ | ||
| private float accumDx = 0f; | ||
| private float accumDy = 0f; | ||
|
|
||
| public GyroAimingHelper(Context context, Listener listener) { | ||
| this(context, listener, DEFAULT_SENSITIVITY); | ||
| } | ||
|
|
||
| public GyroAimingHelper(Context context, Listener listener, float sensitivity) { | ||
| this.context = context.getApplicationContext(); | ||
| this.listener = listener; | ||
| this.sensitivity = sensitivity > 0 ? sensitivity : DEFAULT_SENSITIVITY; | ||
| this.mainHandler = new Handler(Looper.getMainLooper()); | ||
| } | ||
|
|
||
| public void start() { | ||
| if (running) return; | ||
| sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); | ||
| displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); | ||
| if (sensorManager == null) { | ||
| Timber.w("GyroAiming: SensorManager is null"); | ||
| return; | ||
| } | ||
| gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); | ||
| if (gyro == null) { | ||
| Timber.w("GyroAiming: no TYPE_GYROSCOPE sensor on this device"); | ||
| return; | ||
| } | ||
| hasLastTimestamp = false; | ||
| accumDx = 0f; | ||
| accumDy = 0f; | ||
| boolean registered = sensorManager.registerListener(this, gyro, SENSOR_DELAY_US); | ||
| if (!registered) { | ||
| Timber.e("GyroAiming: failed to start, registerListener returned false (sensor=%s)", gyro.getName()); | ||
| return; | ||
| } | ||
| running = true; | ||
| Timber.d("GyroAiming: started (sensor=%s)", gyro.getName()); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public void stop() { | ||
| if (!running) return; | ||
| running = false; | ||
| mainHandler.removeCallbacksAndMessages(null); | ||
| if (sensorManager != null && gyro != null) { | ||
| sensorManager.unregisterListener(this, gyro); | ||
| } | ||
| sensorManager = null; | ||
| displayManager = null; | ||
| gyro = null; | ||
| Timber.d("GyroAiming: stopped"); | ||
| } | ||
unbelievableflavour marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| public boolean isRunning() { | ||
| return running; | ||
| } | ||
|
|
||
| public void setSensitivity(float sensitivity) { | ||
| this.sensitivity = sensitivity > 0 ? sensitivity : DEFAULT_SENSITIVITY; | ||
| } | ||
|
|
||
| @Override | ||
| public void onSensorChanged(SensorEvent event) { | ||
| if (!running) return; | ||
| if (event.sensor.getType() != Sensor.TYPE_GYROSCOPE || listener == null) return; | ||
|
|
||
| long t = event.timestamp; | ||
| if (!hasLastTimestamp) { | ||
| lastTimestampNs = t; | ||
| hasLastTimestamp = true; | ||
| return; | ||
| } | ||
| float dt = (t - lastTimestampNs) * 1e-9f; | ||
| lastTimestampNs = t; | ||
| if (dt <= 0 || dt > 0.5f) return; // skip invalid or huge gaps | ||
|
|
||
| float deviceRadX = event.values[0]; | ||
| float deviceRadY = event.values[1]; | ||
|
|
||
| float radX; | ||
| float radY; | ||
| // Offset by +90deg to match aiming orientation. | ||
| int effectiveRotation = (getDisplayRotation() + 1) & 0x3; | ||
| switch (effectiveRotation) { | ||
| case Surface.ROTATION_90: | ||
| radX = deviceRadY; | ||
| radY = -deviceRadX; | ||
| break; | ||
| case Surface.ROTATION_180: | ||
| radX = -deviceRadX; | ||
| radY = -deviceRadY; | ||
| break; | ||
| case Surface.ROTATION_270: | ||
| radX = -deviceRadY; | ||
| radY = deviceRadX; | ||
| break; | ||
| case Surface.ROTATION_0: | ||
| default: | ||
| radX = deviceRadX; | ||
| radY = deviceRadY; | ||
| break; | ||
| } | ||
|
|
||
| float dx = -radX * dt * sensitivity; | ||
| float dy = radY * dt * sensitivity; | ||
|
|
||
| // Clamp per-frame delta for stability | ||
| dx = clamp(dx, -MAX_DELTA_PER_FRAME, MAX_DELTA_PER_FRAME); | ||
| dy = clamp(dy, -MAX_DELTA_PER_FRAME, MAX_DELTA_PER_FRAME); | ||
|
|
||
| accumDx += dx; | ||
| accumDy += dy; | ||
| int ix = (int) accumDx; | ||
| int iy = (int) accumDy; | ||
| if (ix != 0 || iy != 0) { | ||
| accumDx -= ix; | ||
| accumDy -= iy; | ||
| final int fix = ix; | ||
| final int fiy = iy; | ||
| mainHandler.post(() -> { | ||
| if (!running) return; | ||
| listener.onMouseDelta(fix, fiy); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void onAccuracyChanged(Sensor sensor, int accuracy) {} | ||
|
|
||
| private int getDisplayRotation() { | ||
| if (displayManager == null) return Surface.ROTATION_0; | ||
| Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); | ||
| if (display == null) return Surface.ROTATION_0; | ||
| return display.getRotation(); | ||
| } | ||
|
|
||
| private static float clamp(float v, float lo, float hi) { | ||
| return v < lo ? lo : (v > hi ? hi : v); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.