Air Mouse – Complete Advanced Customisation Guide
This document provides exhaustive instructions for customising every aspect of the Air Mouse system – from simple settings like TCP port and haptic feedback to advanced modifications like adjusting the Madgwick fusion parameter and adding entirely new gestures (e.g., long press). All changes are explained with code snippets, configuration files, and their effects on performance and user experience.
📖 Table of Contents
- Changing the TCP Port
- Adjusting Madgwick Beta (Drift vs Noise Trade‑off)
- Disabling Haptic Feedback
- Changing Sensitivity & Gesture Thresholds
- Adding a New Gesture (e.g., Long Press)
- Changing Sensor Sampling Rate
- Modifying the Debug Overlay
- Customising the PC Server
- Applying Custom Icons & Theme
1. Changing the TCP Port
Why change the port?
- The default port 8080 may already be in use by another application (e.g., a web server, proxy).
- Your network firewall may block port 8080, but allow others.
- You may want to run multiple instances of Air Mouse on the same PC (using different ports).
Step‑by‑step modification
On the Android side (MainActivity.kt)
Locate the companion object block:
companion object {
private const val PORT = 8080 // Change this value
}
Example: change to 8081:
private const val PORT = 8081
On the PC server
If using server.py (console server):
Edit config.json:
{
"host": "0.0.0.0",
"port": 8081,
...
}
If using gui.py (GUI server):
Change the CONFIG dictionary at the top:
CONFIG = {
"host": "0.0.0.0",
"port": 8081,
"sensitivity": 0.5,
}
Important: After changing the port, you must restart both the Android app and the PC server.
Verifying the change
- On PC:
netstat -an | grep 8081should showLISTENING. - On Android: the logcat should show
Connected to <IP>:8081.
Firewall considerations
If you change to a non‑standard port, ensure your firewall allows inbound connections on that port.
2. Adjusting Madgwick Beta (Drift vs Noise Trade‑off)
What is beta?
The Madgwick AHRS algorithm uses a gain parameter beta that controls how aggressively the accelerometer and magnetometer correct the gyroscope integration.
- High
beta(e.g., 0.5 – 1.0):
Strong correction → less drift, but more sensitivity to accelerometer noise (the cursor may jitter when the phone is perfectly still). - Low
beta(e.g., 0.01 – 0.05):
Weak correction → gyro drift becomes noticeable (cursor slowly moves even when phone is stationary), but the movement is smoother (less noise).
Where to change it
In MadgwickFusion.kt, find the constructor:
class MadgwickFusion(private val beta: Float = 0.1f) { ... }
Change the default value, or create a custom instance in SensorService.kt:
private val madgwick = MadgwickFusion(beta = 0.2f) // stronger correction
Empirical tuning
- If you experience drift (cursor moves when phone is still), increase
beta(e.g., 0.15 – 0.25). - If the cursor shakes or jitters when stationary, decrease
beta(e.g., 0.05 – 0.08).
Perfetto verification
You can trace the effect of beta by logging the angular error before and after correction (requires instrumenting the code). In general, a well‑tuned beta makes the cursor stable when still and responsive when moved.
3. Disabling Haptic Feedback
Why disable?
- To save battery (vibration consumes power).
- If you find the vibration distracting or noisy (e.g., in a quiet room).
Method 1 – Using the Settings Dialog (runtime)
In the Android app, tap Settings → uncheck “Enable haptic feedback”. This change is saved immediately and persists across app restarts.
Method 2 – Permanently (by code)
In EnhancedGestureDetector.kt, the vibrate() function checks preferences.isHapticEnabled(). You can hardcode it to always return false:
private fun vibrate(duration: Long) {
// if (preferences.isHapticEnabled()) {
// vibrator.vibrate(...)
// }
// Disabled permanently
}
Or simply comment out the call to vibrate() in the gesture detection methods.
Method 3 – Remove vibration permission from manifest
In AndroidManifest.xml, remove the line:
<uses-permission android:name="android.permission.VIBRATE" />
This will also disable all vibrations (but may cause the app to crash if vibration is attempted – so better to wrap in try‑catch or keep the condition).
4. Changing Sensitivity & Gesture Thresholds
Available settings (already in the UI)
| Setting | Range (default) | Where to change |
|---|---|---|
| Cursor speed | 0.2 – 2.0 (0.5) | Main screen slider |
| Click speed threshold | 0 – 10 rad/s (5.0) | Settings dialog |
| Double‑click interval | 200 – 1000 ms (400) | Settings dialog |
| Right‑click tilt angle | 0 – 90° (45°) | Settings dialog |
| Right‑click hold duration | 100 – 1000 ms (500) | Settings dialog |
| Scroll speed threshold | 0 – 15 m/s² (8.0) | Settings dialog |
| Scroll debounce | 0 – 5 m/s² (2.0) | Settings dialog |
All these are stored in PreferencesDataStore and can be changed programmatically:
prefs.setClickThreshold(4.5f)
prefs.setSensitivity(1.2f)
Changing default values (initial installation)
If you want different defaults for new users, modify the PreferencesDataStore default values:
private val SENSITIVITY = floatPreferencesKey("sensitivity")
// In the flow, change the default:
dataStore.data.map { it[SENSITIVITY] ?: 0.8f } // new default 0.8
Also update the corresponding getters (getSensitivity() etc.) and the Constants.kt.
5. Adding a New Gesture (e.g., Long Press)
Overview
Adding a custom gesture involves three parts:
- Detection – in
EnhancedGestureDetector.kt. - Messaging – in
DataSender.kt(add new message type). - PC handling – in
server.pyorgui.py.
Let’s implement a long press gesture: user holds the phone still for 1 second while it is in a certain orientation (e.g., roll > 30°). This could trigger a middle‑click or a custom action.
Step 1: Detect the gesture in EnhancedGestureDetector.kt
Add a new Gesture enum value:
enum class Gesture {
NONE, CLICK, DOUBLE_CLICK, RIGHT_CLICK, SCROLL_UP, SCROLL_DOWN, LONG_PRESS
}
Add state variables and detection logic:
private var longPressStartTime = 0L
private val longPressDuration = 1000L // 1 second
fun detectLongPress(roll: Float, isMoving: Boolean): Boolean {
val now = System.currentTimeMillis()
// Condition: phone tilted more than 30° and not moving much
if (kotlin.math.abs(roll) > 30f && !isMoving) {
if (longPressStartTime == 0L) {
longPressStartTime = now
} else if (now - longPressStartTime > longPressDuration) {
longPressStartTime = 0L
return true
}
} else {
longPressStartTime = 0L
}
return false
}
The isMoving flag can be derived from gyro magnitude (sum of absolute angular velocities) – you may need to pass it in.
Integrate into the main detect() method or call it separately from SensorService.
Step 2: Add message type in DataSender.kt
Add a new method:
fun sendLongPress() {
val json = JSONObject().apply {
put("type", "longpress")
put("id", System.currentTimeMillis())
}
queue.offer(json.toString())
}
Update isCriticalMessage() to include "longpress" so that ACK and retransmission are applied.
Step 3: Handle on PC server
In server.py (or gui.py), extend the process_message method:
elif t == 'longpress':
self.mouse.click(button='middle') # or any action
await self.send_ack(msg.get('id'), writer)
self.log("🖱️ Long press (middle click)")
If you want a different action (e.g., volume up/down), you can use pyautogui.hotkey('ctrl', 'c') etc.
Step 4: Call the new detector from SensorService
In SensorService.onSensorChanged(), after computing roll and before sending orientation, call:
val isMoving = kotlin.math.abs(gyroX) + kotlin.math.abs(gyroY) + kotlin.math.abs(gyroZ) > 1.0f
if (gestureDetector.detectLongPress(roll, isMoving)) {
gestureCallback?.invoke(Gesture.LONG_PRESS)
}
Testing
- Build and run the Android app.
- Tilt the phone sideways (>30°) and hold still for 1 second.
- Observe the PC server log: should show “Long press (middle click)”.
6. Changing Sensor Sampling Rate
Why change?
- Higher rate (e.g., 100 Hz) – smoother cursor but more battery drain.
- Lower rate (e.g., 20 Hz) – better battery, but cursor may feel less responsive.
Where to change
In SensorService.kt, when registering listeners:
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME)
Replace SENSOR_DELAY_GAME with one of:
| Constant | Period | Approx. Hz |
|---|---|---|
SENSOR_DELAY_FASTEST |
0 ms (as fast as possible) | up to 200 Hz |
SENSOR_DELAY_GAME |
20 ms | 50 Hz |
SENSOR_DELAY_UI |
60 ms | 16 Hz |
SENSOR_DELAY_NORMAL |
200 ms | 5 Hz |
Caution: SENSOR_DELAY_FASTEST may cause high CPU and battery drain. Test on your device.
Dynamic rate switching (battery saver)
The existing BatterySaver already switches between GAME and NORMAL. You can modify its thresholds (idleThresholdMs) in BatterySaver.kt.
7. Modifying the Debug Overlay
What you can change
- Position (top‑left, bottom‑right, etc.)
- Text size, font, colours
- Which sensor values are displayed
Position
In DebugOverlayService.kt, modify the WindowManager.LayoutParams:
params.gravity = Gravity.TOP or Gravity.END // top‑right
params.x = 10
params.y = 100
Appearance
In debug_overlay.xml, change the background, text colour, text size:
<TextView
android:background="#AA000000" // semi‑transparent black
android:textColor="#00FF00" // green text
android:textSize="14sp"
... />
Additional data
To show more sensor values (e.g., roll in degrees, battery percentage), modify updateData() in DebugOverlayService and the corresponding call from MainActivity or SensorService.
8. Customising the PC Server
Changing GUI theme
In gui.py, the colour palette is defined at the top:
self.bg_color = "#1e1e1e" # dark background
self.accent = "#007acc" # blue accent
Change these to any hex colour.
Adding more log detail
In AirMouseServer.log(), you can add timestamps, client IP, etc. The existing logging already includes it.
Changing the mouse controller library
By default, Air Mouse uses pyautogui. You could replace it with:
pynput– more advanced input simulation.win32api(Windows only) – lower latency.Xlib(Linux) – direct X11 calls.
Modify MouseController class accordingly.
Running as a background service (headless)
On Linux/macOS, you can run server.py with nohup:
nohup python server.py &
Or create a systemd service (Linux) for auto‑start.
9. Applying Custom Icons & Theme
Changing the app icon
Replace the images in res/mipmap-* folders (e.g., ic_launcher.png). Use different resolutions:
mipmap-mdpi(48×48)mipmap-hdpi(72×72)mipmap-xhdpi(96×96)mipmap-xxhdpi(144×144)mipmap-xxxhdpi(192×192)
Changing the app theme
In res/values/themes.xml, modify the colour attributes:
<item name="colorPrimary">#FF6200EE</item> <!-- purple -->
<item name="colorPrimaryVariant">#FF3700B3</item>
<item name="colorOnPrimary">#FFFFFFFF</item>
To use a light theme, change the parent:
<style name="Theme.AirMouse" parent="Theme.MaterialComponents.DayNight.LightActionBar">
Renaming the app
Change app_name in res/values/strings.xml.
10. Summary of Customisation Options
| Customisation | File(s) to edit | Skill level |
|---|---|---|
| TCP port | MainActivity.kt, config.json / gui.py |
Easy |
| Madgwick beta | MadgwickFusion.kt |
Medium |
| Haptic feedback | Settings dialog (no code) / EnhancedGestureDetector.kt |
Easy |
| Sensitivity & thresholds | Settings dialog (UI) / PreferencesDataStore.kt |
Easy |
| New gesture | EnhancedGestureDetector.kt, DataSender.kt, server.py |
Hard |
| Sampling rate | SensorService.kt |
Medium |
| Debug overlay appearance | DebugOverlayService.kt, debug_overlay.xml |
Easy |
| PC server theme | gui.py (colours) |
Easy |
| App icon & theme | res/mipmap-*, themes.xml, strings.xml |
Easy |
Final Notes
- Always back up your code before making advanced changes.
- Test incrementally – change one parameter at a time and verify behaviour.
- Document your customisations – they may be required in your project report.
This guide equips you to tailor Air Mouse to your exact needs – whether for better performance, new features, or a personalised user interface. Enjoy hacking!
Xet Storage Details
- Size:
- 13.7 kB
- Xet hash:
- 5e5755875bb1b3baeff28fc841b765b7d1920c58aca423192ca95c5019664b2d
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.