Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions app/src/main/java/com/winlator/winhandler/WinHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class WinHandler {
private final ArrayList<Integer> xinputProcesses;
private final XServer xServer;
private final XServerView xServerView;
private byte[] sdlButtons = new byte[15];

private InputControlsView inputControlsView;
private Thread rumblePollerThread;
Expand Down Expand Up @@ -546,8 +547,12 @@ public void start() {
} catch (UnknownHostException e2) {
}
}
this.running = true;
startSendThread();

if(!this.running) {
this.running = true;
startSendThread();
}
Comment on lines +551 to +554
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make send-thread startup guard atomic and single-sourced.

Line 551 and Line 575 both do unsynchronized check-then-set on running, so concurrent start() calls can still pass the guard and invoke startSendThread() twice. Consolidate to one guarded block and make visibility/thread-safety explicit.

Suggested fix
@@
-    private boolean running;
+    private volatile boolean running;
+    private final Object lifecycleLock = new Object();
@@
-        if(!this.running) {
-            this.running = true;
-            startSendThread();
-        }
+        synchronized (lifecycleLock) {
+            if (this.running) return;
+            this.running = true;
+            startSendThread();
+        }
@@
-        if(!running) {
-            running = true;
-            startSendThread();
-        }
+        // send thread already started in the lifecycle guard above

Also applies to: 575-578

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/winlator/winhandler/WinHandler.java` around lines 551 -
554, The two unsynchronized check-then-set guards around the field running
permit concurrent start() calls to invoke startSendThread() twice; replace both
guards with a single thread-safe startup guard: change running to an
AtomicBoolean (or make it volatile and protect with a synchronized block) and
use compareAndSet(false, true) in start() to atomically flip the flag and call
startSendThread() exactly once; remove the duplicate check at the other site so
startSendThread() is only invoked from the single guarded path (referencing the
running field, start() method, and startSendThread()).


Executors.newSingleThreadExecutor().execute(() -> {
try {
DatagramSocket datagramSocket = new DatagramSocket((SocketAddress) null);
Expand All @@ -567,8 +572,10 @@ public void start() {
});

startRumblePoller();
running = true;
startSendThread();
if(!running) {
running = true;
startSendThread();
}
}

private void startRumblePoller() {
Expand Down Expand Up @@ -811,7 +818,7 @@ private void sendMemoryFileState(ExternalController controller, MappedByteBuffer
buffer.putShort((short)lAxis);
buffer.putShort((short)rAxis);
// --- Buttons and D-Pad are perfect. No changes here. ---
byte[] sdlButtons = new byte[15];

sdlButtons[0] = state.isPressed(0) ? (byte)1 : (byte)0; // A
sdlButtons[1] = state.isPressed(1) ? (byte)1 : (byte)0; // B
sdlButtons[2] = state.isPressed(2) ? (byte)1 : (byte)0; // X
Expand Down Expand Up @@ -851,21 +858,21 @@ public void sendVirtualGamepadState(GamepadState state) {
gamepadBuffer.putShort((short)rAxis);

// Buttons & D-Pad
byte[] sdlButtons = new byte[15];
sdlButtons[0] = state.isPressed(0) ? (byte)1 : (byte)0; // A
sdlButtons[1] = state.isPressed(1) ? (byte)1 : (byte)0; // B
sdlButtons[2] = state.isPressed(2) ? (byte)1 : (byte)0; // X
sdlButtons[3] = state.isPressed(3) ? (byte)1 : (byte)0; // Y
sdlButtons[9] = state.isPressed(4) ? (byte)1 : (byte)0; // Left Bumper
sdlButtons[10] = state.isPressed(5) ? (byte)1 : (byte)0; // Right Bumper
sdlButtons[4] = state.isPressed(6) ? (byte)1 : (byte)0; // Select/Back
sdlButtons[6] = state.isPressed(7) ? (byte)1 : (byte)0; // Start
sdlButtons[7] = state.isPressed(8) ? (byte)1 : (byte)0; // Left Stick
sdlButtons[8] = state.isPressed(9) ? (byte)1 : (byte)0; // Right Stick
sdlButtons[11] = state.dpad[0] ? (byte)1 : (byte)0; // DPAD_UP
sdlButtons[12] = state.dpad[2] ? (byte)1 : (byte)0; // DPAD_DOWN
sdlButtons[13] = state.dpad[3] ? (byte)1 : (byte)0; // DPAD_LEFT
sdlButtons[14] = state.dpad[1] ? (byte)1 : (byte)0; // DPAD_RIGHT

this.sdlButtons[0] = state.isPressed(0) ? (byte)1 : (byte)0; // A
this.sdlButtons[1] = state.isPressed(1) ? (byte)1 : (byte)0; // B
this.sdlButtons[2] = state.isPressed(2) ? (byte)1 : (byte)0; // X
this.sdlButtons[3] = state.isPressed(3) ? (byte)1 : (byte)0; // Y
this.sdlButtons[9] = state.isPressed(4) ? (byte)1 : (byte)0; // Left Bumper
this.sdlButtons[10] = state.isPressed(5) ? (byte)1 : (byte)0; // Right Bumper
this.sdlButtons[4] = state.isPressed(6) ? (byte)1 : (byte)0; // Select/Back
this.sdlButtons[6] = state.isPressed(7) ? (byte)1 : (byte)0; // Start
this.sdlButtons[7] = state.isPressed(8) ? (byte)1 : (byte)0; // Left Stick
this.sdlButtons[8] = state.isPressed(9) ? (byte)1 : (byte)0; // Right Stick
this.sdlButtons[11] = state.dpad[0] ? (byte)1 : (byte)0; // DPAD_UP
this.sdlButtons[12] = state.dpad[2] ? (byte)1 : (byte)0; // DPAD_DOWN
this.sdlButtons[13] = state.dpad[3] ? (byte)1 : (byte)0; // DPAD_LEFT
this.sdlButtons[14] = state.dpad[1] ? (byte)1 : (byte)0; // DPAD_RIGHT
gamepadBuffer.put(sdlButtons);
gamepadBuffer.put((byte)0); // Ignored HAT value
}
Expand Down
Loading