Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ add_library(winlator SHARED
winlator/drawable.c
winlator/native_content_io.cpp
winlator/gpu_image.c
winlator/ring_fence.c
winlator/sync_fence.c
winlator/sysvshared_memory.c
winlator/xconnector_epoll.c
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/cpp/winlator/fakeinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static constexpr uint8_t GAMEPAD_BUTTON_COUNT = 11;
static constexpr uint32_t FAKE_INPUT_RING_MAGIC = 0x46494252;
static constexpr uint32_t FAKE_INPUT_RING_VERSION = 2;
static constexpr uint32_t FAKE_INPUT_EVENT_SIZE = sizeof(struct input_event);
static constexpr uint32_t FAKE_INPUT_RING_CAPACITY = 512;
static constexpr uint32_t FAKE_INPUT_RING_CAPACITY = 4096;
static constexpr unsigned int FAKE_INPUT_MAJOR = 13;
static constexpr unsigned int FAKE_INPUT_EVENT_MINOR_BASE = 64;
static constexpr unsigned int FAKE_INPUT_JS_MINOR_BASE = 0;
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/cpp/winlator/ring_fence.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <jni.h>
#include <stdatomic.h>

// Store-store barrier (dmb ish on arm64) so ring event/snapshot payload stores
// are visible to the guest-side reader before the sequence word that publishes
// them. Pairs with the __ATOMIC_ACQUIRE loads in fakeinput.cpp.
JNIEXPORT void JNICALL
Java_com_winlator_cmod_runtime_input_controls_FakeInputWriter_nativeStoreFence(
JNIEnv *env, jclass clazz) {
(void)env;
(void)clazz;
atomic_thread_fence(memory_order_release);
}
3 changes: 3 additions & 0 deletions app/src/main/runtime/display/XServerDisplayActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -4869,6 +4869,9 @@ public void onDrawerSlide() {
@Override
public void onDrawerOpened() {
releasePointerCapture();
if (winHandler != null) {
winHandler.neutralizeControllers();
}
renderDrawerMenu();
if (drawerStateHolder != null) {
drawerStateHolder.resetMenuNav();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1007,20 +1007,20 @@ private int execGuestProgram() {
Log.d("GuestLauncher", "nativeLibDir: " + nativeLibDir);
Log.d("GuestLauncher", "fakeinputSrc exists: " + fakeinputSrc.exists());
Log.d("GuestLauncher", "fakeinputDest: " + fakeinputDest.getAbsolutePath());
if (!fakeinputDest.exists()) {
try {
if (fakeinputSrc.exists()) {
FileUtils.copy(fakeinputSrc, fakeinputDest);
Log.d("GuestLauncher", "Copied libfakeinput.so to imagefs");
} else {
Log.e(
"GuestLauncher",
"libfakeinput.so NOT FOUND in APK: " + fakeinputSrc.getAbsolutePath());
}
} catch (Exception e) {
Log.e("GuestLauncher", "Failed to copy libfakeinput.so: " + e.getMessage());
e.printStackTrace();
try {
if (fakeinputSrc.exists()) {
// Refresh every launch so the guest reader can never skew from the
// app-side ring writer (a same-size rebuild defeats length checks).
FileUtils.copy(fakeinputSrc, fakeinputDest);
Log.d("GuestLauncher", "Copied libfakeinput.so to imagefs");
} else if (!fakeinputDest.exists()) {
Log.e(
"GuestLauncher",
"libfakeinput.so NOT FOUND in APK: " + fakeinputSrc.getAbsolutePath());
}
} catch (Exception e) {
Log.e("GuestLauncher", "Failed to copy libfakeinput.so: " + e.getMessage());
e.printStackTrace();
}
Log.d("GuestLauncher", "fakeinputDest exists after copy: " + fakeinputDest.exists());

Expand Down
29 changes: 29 additions & 0 deletions app/src/main/runtime/display/winhandler/WinHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,35 @@ public void sendGamepadState(ExternalController controller) {
if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced();
}

// Menu owns the controller while open; zero tracked state and push it once so nothing stays held in the guest.
public void neutralizeControllers() {
for (ExternalController controller : this.controllers.values()) {
if (controller == null) {
continue;
}
clearGamepadState(controller.state);
clearGamepadState(controller.remappedState);
int slot = assignSlot(controller.getDeviceId());
if (slot >= 0 && this.writers[slot] != null) {
try {
this.writers[slot].writeGamepadState(controller.state);
} catch (IOException ignored) {
}
}
}
}

private static void clearGamepadState(GamepadState state) {
state.thumbLX = 0;
state.thumbLY = 0;
state.thumbRX = 0;
state.thumbRY = 0;
state.triggerL = 0;
state.triggerR = 0;
state.buttons = 0;
state.dpad[0] = state.dpad[1] = state.dpad[2] = state.dpad[3] = false;
}

private void writeControllerGamepadState(
ExternalController controller, boolean applyGyroOverlay) {
ExternalController profileController;
Expand Down
18 changes: 14 additions & 4 deletions app/src/main/runtime/input/controls/FakeInputWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class FakeInputWriter {
private static final int BUFFER_SIZE = 768;
private static final int EVENT_SIZE = 24;
private static final int MAX_FAKE_INPUT_SLOTS = 4;
private static final int RING_CAPACITY_EVENTS = 512;
private static final int RING_CAPACITY_EVENTS = 4096;
private static final int RING_HEADER_SIZE = 64;
private static final int RING_SIZE = RING_HEADER_SIZE + (RING_CAPACITY_EVENTS * EVENT_SIZE);
private static final int RING_MAGIC = 0x46494252; // FIBR
Expand Down Expand Up @@ -49,6 +49,12 @@ public class FakeInputWriter {
private static final String TAG = "FakeInputWriter";
private static final Object RING_LOCK = new Object();
private static final RingSlot[] RING_SLOTS = new RingSlot[MAX_FAKE_INPUT_SLOTS];

static {
System.loadLibrary("winlator");
}

private static native void nativeStoreFence();
private final File eventFile;
private final int slot;
private int prevHatX;
Expand Down Expand Up @@ -363,15 +369,15 @@ private boolean flushBufferToRing() {
// Publish the resulting absolute state. prev* now hold the post-update
// values, i.e. exactly the state the events just written transition to.
writeSnapshotLocked(ring);
nativeStoreFence();
ring.putLong(RING_WRITE_SEQ_OFFSET, writeSeq);
}
return true;
}

// Publishes the full absolute controller state for the native reader to replay
// as a keyframe. seqlock: bump to odd, write fields, bump to even. Mirrors the
// write_seq publication model (plain mapped-buffer stores); the reader retries
// on a torn read.
// as a keyframe. seqlock: bump to odd, write fields, bump to even, with
// store-store fences so the reader's acquire loads see consistent payloads.
private void writeSnapshotLocked(ByteBuffer ring) {
int buttons = 0;
for (int i = 0; i < BUTTON_MAP.length; i++) {
Expand All @@ -381,6 +387,7 @@ private void writeSnapshotLocked(ByteBuffer ring) {
}
long seq = ring.getLong(RING_SNAPSHOT_SEQ_OFFSET);
ring.putLong(RING_SNAPSHOT_SEQ_OFFSET, seq + 1); // odd: write in progress
nativeStoreFence();
ring.putInt(RING_SNAPSHOT_BUTTONS_OFFSET, buttons);
// Axis order must match the native kSnapshotAxisCodes:
// X, Y, RX, RY, GAS(=triggerR), BRAKE(=triggerL), HAT0X, HAT0Y.
Expand All @@ -392,6 +399,7 @@ private void writeSnapshotLocked(ByteBuffer ring) {
ring.putShort(RING_SNAPSHOT_AXES_OFFSET + 10, clampShort(this.prevTriggerL));
ring.putShort(RING_SNAPSHOT_AXES_OFFSET + 12, clampShort(this.prevHatX));
ring.putShort(RING_SNAPSHOT_AXES_OFFSET + 14, clampShort(this.prevHatY));
nativeStoreFence();
ring.putLong(RING_SNAPSHOT_SEQ_OFFSET, seq + 2); // even: write complete
}

Expand All @@ -408,10 +416,12 @@ private static short clampShort(int value) {
private static void clearSnapshotLocked(ByteBuffer ring) {
long seq = ring.getLong(RING_SNAPSHOT_SEQ_OFFSET);
ring.putLong(RING_SNAPSHOT_SEQ_OFFSET, seq + 1);
nativeStoreFence();
ring.putInt(RING_SNAPSHOT_BUTTONS_OFFSET, 0);
for (int i = 0; i < 8; i++) {
ring.putShort(RING_SNAPSHOT_AXES_OFFSET + (i * 2), (short) 0);
}
nativeStoreFence();
ring.putLong(RING_SNAPSHOT_SEQ_OFFSET, seq + 2);
}

Expand Down