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
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package cn.classfun.droidvm.ui.vm.display.base;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/** Intercept window-wide captured events before a focused IME child can consume them. */
public final class CapturedPointerLayout extends LinearLayout {
public interface Handler {
boolean onEvent(MotionEvent event);
void onCaptureChanged(boolean captured);
}
private Handler handler;
public CapturedPointerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusableInTouchMode(true);
}
public void setCaptureHandler(Handler handler) { this.handler = handler; }
@Override public boolean dispatchCapturedPointerEvent(MotionEvent event) {
return handler != null && handler.onEvent(event) || super.dispatchCapturedPointerEvent(event);
}
@Override public void onPointerCaptureChange(boolean captured) {
super.onPointerCaptureChange(captured);
if (handler != null) handler.onCaptureChanged(captured);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package cn.classfun.droidvm.ui.vm.display.base;

import java.util.HashMap;
import java.util.Map;

/** A guest button remains held until every host device holding it releases it. */
public final class PointerButtons {
private final Map<Integer, Integer> devices = new HashMap<>();
private final PointerEventQueue.Output output;
private int combined;
public PointerButtons(PointerEventQueue.Output output) { this.output = output; }
public void set(int deviceId, int buttons) {
if (buttons == 0) devices.remove(deviceId);
else devices.put(deviceId, buttons & 31);
int next = 0;
for (int value : devices.values()) next |= value;
int changed = combined ^ next;
combined = next;
for (int bit = 0; bit < 5; bit++)
if ((changed & (1 << bit)) != 0) output.button(0x110 + bit, (next & (1 << bit)) != 0);
}
public void reset() {
devices.clear();
int held = combined; combined = 0;
for (int bit = 0; bit < 5; bit++)
if ((held & (1 << bit)) != 0) output.button(0x110 + bit, false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package cn.classfun.droidvm.ui.vm.display.base;

import java.util.ArrayDeque;
import java.util.concurrent.Executor;

/** Coalesce adjacent deltas without moving motion across a button or wheel boundary. */
public final class PointerEventQueue {
public interface Output {
void move(int x, int y);
void button(int code, boolean down);
void scroll(int vertical, int horizontal);
default void failed(RuntimeException error) { error.printStackTrace(); }
}
private static final int MOVE = 0, BUTTON = 1, SCROLL = 2;
private static final class Event {
final int kind;
int x, y;
Event(int kind, int x, int y) { this.kind = kind; this.x = x; this.y = y; }
}
private final ArrayDeque<Event> pending = new ArrayDeque<>();
private final Executor executor;
private final Output output;
private boolean scheduled, closed;
public PointerEventQueue(Executor executor, Output output) {
this.executor = executor; this.output = output;
}
public void move(int x, int y) { if (x != 0 || y != 0) add(MOVE, x, y); }
public void button(int code, boolean down) { add(BUTTON, code, down ? 1 : 0); }
public void scroll(int v, int h) { if (v != 0 || h != 0) add(SCROLL, v, h); }
private synchronized void add(int kind, int x, int y) {
if (closed) return;
Event tail = pending.peekLast();
if (tail != null && tail.kind == kind && (kind == MOVE
|| kind == SCROLL && sameDirection(tail.x, x) && sameDirection(tail.y, y))) {
tail.x += x; tail.y += y;
} else pending.addLast(new Event(kind, x, y));
if (!scheduled) {
scheduled = true;
executor.execute(this::drain);
}
}
private static boolean sameDirection(int before, int after) {
return before == 0 || after == 0 || (before > 0) == (after > 0);
}
private void drain() {
for (;;) {
Event event;
synchronized (this) {
event = pending.pollFirst();
if (event == null) { scheduled = false; return; }
}
try {
if (event.kind == BUTTON) output.button(event.x, event.y != 0);
else if (event.x != 0 || event.y != 0) {
if (event.kind == MOVE) output.move(event.x, event.y);
else output.scroll(event.x, event.y);
}
} catch (RuntimeException error) {
// A failed frame must not prevent later button releases from draining.
output.failed(error);
}
}
}
/** Reject new input, but retain releases already queued by the owner. */
public synchronized void close() { closed = true; }
}
Loading
Loading