diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 18933e7b..6aab79d7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -54,7 +54,7 @@ public class Controller { sender = new DeviceMessageSender(connection); try { - GameController.loadNativeLibraries(); + UinputDevice.loadNativeLibraries(); gameControllersEnabled = true; } catch (UnsatisfiedLinkError e) { Ln.e("Could not load native libraries. Game controllers will be disabled.", e); diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index dc139e17..2725c49b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -9,156 +9,7 @@ import com.sun.jna.Structure; import java.util.Arrays; import java.util.List; -public final class GameController { - public static final int DEVICE_ADDED = 0; - public static final int DEVICE_REMOVED = 1; - - private static final int UINPUT_MAX_NAME_SIZE = 80; - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class InputId extends Structure { - public short bustype = 0; - public short vendor = 0; - public short product = 0; - public short version = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("bustype", "vendor", "product", "version"); - } - } - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class UinputSetup extends Structure { - public InputId id; - public byte[] name = new byte[UINPUT_MAX_NAME_SIZE]; - public int ffEffectsMax = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("id", "name", "ffEffectsMax"); - } - } - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class InputAbsinfo extends Structure { - public int value = 0; - public int minimum = 0; - public int maximum = 0; - public int fuzz = 0; - public int flat = 0; - public int resolution = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("value", "minimum", "maximum", "fuzz", "flat", "resolution"); - } - }; - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class UinputAbsSetup extends Structure { - public short code; - public InputAbsinfo absinfo; - - @Override - protected List getFieldOrder() { - return Arrays.asList("code", "absinfo"); - } - }; - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class InputEvent32 extends Structure { - public long time = 0; - public short type = 0; - public short code = 0; - public int value = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("time", "type", "code", "value"); - } - } - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class InputEvent64 extends Structure { - public long sec = 0; - public long usec = 0; - public short type = 0; - public short code = 0; - public int value = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("sec", "usec", "type", "code", "value"); - } - } - - private static final int IOC_NONE = 0; - private static final int IOC_WRITE = 1; - - private static final int IOC_DIRSHIFT = 30; - private static final int IOC_TYPESHIFT = 8; - private static final int IOC_NRSHIFT = 0; - private static final int IOC_SIZESHIFT = 16; - - private static int ioc(int dir, int type, int nr, int size) { - return (dir << IOC_DIRSHIFT) - | (type << IOC_TYPESHIFT) - | (nr << IOC_NRSHIFT) - | (size << IOC_SIZESHIFT); - } - - private static int io(int type, int nr, int size) { - return ioc(IOC_NONE, type, nr, size); - } - - private static int iow(int type, int nr, int size) { - return ioc(IOC_WRITE, type, nr, size); - } - - private static final int O_WRONLY = 01; - private static final int O_NONBLOCK = 04000; - - private static final int BUS_USB = 0x03; - - private static final int UINPUT_IOCTL_BASE = 'U'; - - private static final int UI_SET_EVBIT = iow(UINPUT_IOCTL_BASE, 100, 4); - private static final int UI_SET_KEYBIT = iow(UINPUT_IOCTL_BASE, 101, 4); - private static final int UI_SET_ABSBIT = iow(UINPUT_IOCTL_BASE, 103, 4); - private static final int UI_ABS_SETUP = iow(UINPUT_IOCTL_BASE, 4, new UinputAbsSetup().size()); - - private static final int UI_DEV_SETUP = iow(UINPUT_IOCTL_BASE, 3, new UinputSetup().size()); - private static final int UI_DEV_CREATE = io(UINPUT_IOCTL_BASE, 1, 0); - private static final int UI_DEV_DESTROY = io(UINPUT_IOCTL_BASE, 2, 0); - - private static final short EV_SYN = 0x00; - private static final short EV_KEY = 0x01; - private static final short EV_ABS = 0x03; - - private static final short SYN_REPORT = 0x00; - - private static final short BTN_A = 0x130; - private static final short BTN_B = 0x131; - private static final short BTN_X = 0x133; - private static final short BTN_Y = 0x134; - private static final short BTN_TL = 0x136; - private static final short BTN_TR = 0x137; - private static final short BTN_SELECT = 0x13a; - private static final short BTN_START = 0x13b; - private static final short BTN_MODE = 0x13c; - private static final short BTN_THUMBL = 0x13d; - private static final short BTN_THUMBR = 0x13e; - - private static final short ABS_X = 0x00; - private static final short ABS_Y = 0x01; - private static final short ABS_Z = 0x02; - private static final short ABS_RX = 0x03; - private static final short ABS_RY = 0x04; - private static final short ABS_RZ = 0x05; - private static final short ABS_HAT0X = 0x10; - private static final short ABS_HAT0Y = 0x11; - +public final class GameController extends UinputDevice { private static final short XBOX_BTN_A = BTN_A; private static final short XBOX_BTN_B = BTN_B; private static final short XBOX_BTN_X = BTN_X; @@ -203,28 +54,11 @@ public final class GameController { public static final int SDL_CONTROLLER_BUTTON_DPAD_LEFT = 13; public static final int SDL_CONTROLLER_BUTTON_DPAD_RIGHT = 14; - private int fd; - - public interface LibC extends Library { - LibC FN = (LibC) Native.load("c", LibC.class); - - int open(String pathname, int flags); - int ioctl(int fd, long request, Object... args); - long write(int fd, Pointer buf, long count); - int close(int fd); - } - - public static void loadNativeLibraries() { - GameController.LibC.FN.write(1, null, 0); - } - public GameController() { - fd = LibC.FN.open("/dev/uinput", O_WRONLY | O_NONBLOCK); - if (fd == -1) { - throw new RuntimeException("Couldn't open uinput device."); - } + setup(); + } - LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_KEY); + protected void setupKeys() { addKey(XBOX_BTN_A); addKey(XBOX_BTN_B); addKey(XBOX_BTN_X); @@ -236,8 +70,13 @@ public final class GameController { addKey(XBOX_BTN_GUIDE); addKey(XBOX_BTN_LS); addKey(XBOX_BTN_RS); + } - LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_ABS); + protected boolean hasKeys() { + return true; + } + + protected void setupAbs() { addAbs(XBOX_ABS_LSX, -32768, 32767, 16, 128); addAbs(XBOX_ABS_LSY, -32768, 32767, 16, 128); addAbs(XBOX_ABS_RSX, -32768, 32767, 16, 128); @@ -248,87 +87,22 @@ public final class GameController { // but allow higher precision (eg. Xbox One controller) addAbs(XBOX_ABS_LT, 0, 32767, 0, 0); addAbs(XBOX_ABS_RT, 0, 32767, 0, 0); - - UinputSetup usetup = new UinputSetup(); - usetup.id.bustype = BUS_USB; - usetup.id.vendor = 0x045e; - usetup.id.product = 0x028e; - byte[] name = "Microsoft X-Box 360 pad".getBytes(); - System.arraycopy(name, 0, usetup.name, 0, name.length); - - if (LibC.FN.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { - close(); - throw new RuntimeException("Couldn't setup uinput device."); - } - - if (LibC.FN.ioctl(fd, UI_DEV_CREATE) == -1) { - close(); - throw new RuntimeException("Couldn't create uinput device."); - } } - public void close() { - if (fd != -1) { - LibC.FN.ioctl(fd, UI_DEV_DESTROY); - LibC.FN.close(fd); - fd = -1; - } + protected boolean hasAbs() { + return true; } - private void addKey(int key) { - if (LibC.FN.ioctl(fd, UI_SET_KEYBIT, key) == -1) { - Ln.e("Could not add key event."); - } + protected short getVendor() { + return 0x045e; } - private void addAbs(short code, int minimum, int maximum, int fuzz, int flat) { - if (LibC.FN.ioctl(fd, UI_SET_ABSBIT, code) == -1) { - Ln.e("Could not add absolute event."); - } - - UinputAbsSetup absSetup = new UinputAbsSetup(); - - absSetup.code = code; - absSetup.absinfo.minimum = minimum; - absSetup.absinfo.maximum = maximum; - absSetup.absinfo.fuzz = fuzz; - absSetup.absinfo.flat = flat; - - if (LibC.FN.ioctl(fd, UI_ABS_SETUP, absSetup) == -1) { - Ln.e("Could not set absolute event info."); - } + protected short getProduct() { + return 0x028e; } - private static void emit32(int fd, short type, short code, int val) { - InputEvent32 ie = new InputEvent32(); - - ie.type = type; - ie.code = code; - ie.value = val; - - ie.write(); - - LibC.FN.write(fd, ie.getPointer(), ie.size()); - } - - private static void emit64(int fd, short type, short code, int val) { - InputEvent64 ie = new InputEvent64(); - - ie.type = type; - ie.code = code; - ie.value = val; - - ie.write(); - - LibC.FN.write(fd, ie.getPointer(), ie.size()); - } - - private static void emit(int fd, short type, short code, int val) { - if (Platform.is64Bit()) { - emit64(fd, type, code, val); - } else { - emit32(fd, type, code, val); - } + protected String getName() { + return "Microsoft X-Box 360 pad"; } private static short translateAxis(int axis) { @@ -397,8 +171,8 @@ public final class GameController { } public void setAxis(int axis, int value) { - emit(fd, EV_ABS, translateAxis(axis), value); - emit(fd, EV_SYN, SYN_REPORT, 0); + emitAbs(translateAxis(axis), value); + emitReport(); } public void setButton(int button, int state) { @@ -406,24 +180,24 @@ public final class GameController { switch (button) { case SDL_CONTROLLER_BUTTON_DPAD_UP: - emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? -1 : 0); + emitAbs(XBOX_ABS_DPADY, state != 0 ? -1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? 1 : 0); + emitAbs(XBOX_ABS_DPADY, state != 0 ? 1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: - emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? -1 : 0); + emitAbs(XBOX_ABS_DPADX, state != 0 ? -1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? 1 : 0); + emitAbs(XBOX_ABS_DPADX, state != 0 ? 1 : 0); break; default: - emit(fd, EV_KEY, translateButton(button), state); + emitKey(translateButton(button), state); } - emit(fd, EV_SYN, SYN_REPORT, 0); + emitReport(); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java new file mode 100644 index 00000000..9cb8498f --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java @@ -0,0 +1,296 @@ +package com.genymobile.scrcpy; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Platform; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +public abstract class UinputDevice { + public static final int DEVICE_ADDED = 0; + public static final int DEVICE_REMOVED = 1; + + private static final int UINPUT_MAX_NAME_SIZE = 80; + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputId extends Structure { + public short bustype = 0; + public short vendor = 0; + public short product = 0; + public short version = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("bustype", "vendor", "product", "version"); + } + } + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class UinputSetup extends Structure { + public InputId id; + public byte[] name = new byte[UINPUT_MAX_NAME_SIZE]; + public int ffEffectsMax = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("id", "name", "ffEffectsMax"); + } + } + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputAbsinfo extends Structure { + public int value = 0; + public int minimum = 0; + public int maximum = 0; + public int fuzz = 0; + public int flat = 0; + public int resolution = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("value", "minimum", "maximum", "fuzz", "flat", "resolution"); + } + }; + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class UinputAbsSetup extends Structure { + public short code; + public InputAbsinfo absinfo; + + @Override + protected List getFieldOrder() { + return Arrays.asList("code", "absinfo"); + } + }; + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputEvent32 extends Structure { + public long time = 0; + public short type = 0; + public short code = 0; + public int value = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("time", "type", "code", "value"); + } + } + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputEvent64 extends Structure { + public long sec = 0; + public long usec = 0; + public short type = 0; + public short code = 0; + public int value = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("sec", "usec", "type", "code", "value"); + } + } + + private static final int IOC_NONE = 0; + private static final int IOC_WRITE = 1; + + private static final int IOC_DIRSHIFT = 30; + private static final int IOC_TYPESHIFT = 8; + private static final int IOC_NRSHIFT = 0; + private static final int IOC_SIZESHIFT = 16; + + private static int ioc(int dir, int type, int nr, int size) { + return (dir << IOC_DIRSHIFT) + | (type << IOC_TYPESHIFT) + | (nr << IOC_NRSHIFT) + | (size << IOC_SIZESHIFT); + } + + private static int io(int type, int nr, int size) { + return ioc(IOC_NONE, type, nr, size); + } + + private static int iow(int type, int nr, int size) { + return ioc(IOC_WRITE, type, nr, size); + } + + private static final int O_WRONLY = 01; + private static final int O_NONBLOCK = 04000; + + private static final int BUS_USB = 0x03; + + private static final int UINPUT_IOCTL_BASE = 'U'; + + private static final int UI_SET_EVBIT = iow(UINPUT_IOCTL_BASE, 100, 4); + private static final int UI_SET_KEYBIT = iow(UINPUT_IOCTL_BASE, 101, 4); + private static final int UI_SET_ABSBIT = iow(UINPUT_IOCTL_BASE, 103, 4); + private static final int UI_ABS_SETUP = iow(UINPUT_IOCTL_BASE, 4, new UinputAbsSetup().size()); + + private static final int UI_DEV_SETUP = iow(UINPUT_IOCTL_BASE, 3, new UinputSetup().size()); + private static final int UI_DEV_CREATE = io(UINPUT_IOCTL_BASE, 1, 0); + private static final int UI_DEV_DESTROY = io(UINPUT_IOCTL_BASE, 2, 0); + + private static final short EV_SYN = 0x00; + private static final short EV_KEY = 0x01; + private static final short EV_ABS = 0x03; + + private static final short SYN_REPORT = 0x00; + + protected static final short BTN_A = 0x130; + protected static final short BTN_B = 0x131; + protected static final short BTN_X = 0x133; + protected static final short BTN_Y = 0x134; + protected static final short BTN_TL = 0x136; + protected static final short BTN_TR = 0x137; + protected static final short BTN_SELECT = 0x13a; + protected static final short BTN_START = 0x13b; + protected static final short BTN_MODE = 0x13c; + protected static final short BTN_THUMBL = 0x13d; + protected static final short BTN_THUMBR = 0x13e; + + protected static final short ABS_X = 0x00; + protected static final short ABS_Y = 0x01; + protected static final short ABS_Z = 0x02; + protected static final short ABS_RX = 0x03; + protected static final short ABS_RY = 0x04; + protected static final short ABS_RZ = 0x05; + protected static final short ABS_HAT0X = 0x10; + protected static final short ABS_HAT0Y = 0x11; + + private int fd = -1; + + public interface LibC extends Library { + LibC FN = (LibC) Native.load("c", LibC.class); + + int open(String pathname, int flags); + int ioctl(int fd, long request, Object... args); + long write(int fd, Pointer buf, long count); + int close(int fd); + } + + public static void loadNativeLibraries() { + UinputDevice.LibC.FN.write(1, null, 0); + } + + protected void setup() { + fd = LibC.FN.open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (fd == -1) { + throw new RuntimeException("Couldn't open uinput device."); + } + + if (hasKeys()) + { + LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_KEY); + setupKeys(); + } + + if (hasAbs()) + { + LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_ABS); + setupAbs(); + } + + UinputSetup usetup = new UinputSetup(); + usetup.id.bustype = BUS_USB; + usetup.id.vendor = getVendor(); + usetup.id.product = getProduct(); + byte[] name = getName().getBytes(); + System.arraycopy(name, 0, usetup.name, 0, name.length); + + if (LibC.FN.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { + close(); + throw new RuntimeException("Couldn't setup uinput device."); + } + + if (LibC.FN.ioctl(fd, UI_DEV_CREATE) == -1) { + close(); + throw new RuntimeException("Couldn't create uinput device."); + } + } + + public void close() { + if (fd != -1) { + LibC.FN.ioctl(fd, UI_DEV_DESTROY); + LibC.FN.close(fd); + fd = -1; + } + } + + protected abstract void setupKeys(); + protected abstract boolean hasKeys(); + protected abstract void setupAbs(); + protected abstract boolean hasAbs(); + protected abstract short getVendor(); + protected abstract short getProduct(); + protected abstract String getName(); + + protected void addKey(int key) { + if (LibC.FN.ioctl(fd, UI_SET_KEYBIT, key) == -1) { + Ln.e("Could not add key event."); + } + } + + protected void addAbs(short code, int minimum, int maximum, int fuzz, int flat) { + if (LibC.FN.ioctl(fd, UI_SET_ABSBIT, code) == -1) { + Ln.e("Could not add absolute event."); + } + + UinputAbsSetup absSetup = new UinputAbsSetup(); + + absSetup.code = code; + absSetup.absinfo.minimum = minimum; + absSetup.absinfo.maximum = maximum; + absSetup.absinfo.fuzz = fuzz; + absSetup.absinfo.flat = flat; + + if (LibC.FN.ioctl(fd, UI_ABS_SETUP, absSetup) == -1) { + Ln.e("Could not set absolute event info."); + } + } + + private static void emit32(int fd, short type, short code, int val) { + InputEvent32 ie = new InputEvent32(); + + ie.type = type; + ie.code = code; + ie.value = val; + + ie.write(); + + LibC.FN.write(fd, ie.getPointer(), ie.size()); + } + + private static void emit64(int fd, short type, short code, int val) { + InputEvent64 ie = new InputEvent64(); + + ie.type = type; + ie.code = code; + ie.value = val; + + ie.write(); + + LibC.FN.write(fd, ie.getPointer(), ie.size()); + } + + private static void emit(int fd, short type, short code, int val) { + if (Platform.is64Bit()) { + emit64(fd, type, code, val); + } else { + emit32(fd, type, code, val); + } + } + + protected void emitAbs(short abs, int value) { + emit(fd, EV_ABS, abs, value); + } + + protected void emitKey(short key, int state) { + emit(fd, EV_KEY, key, state); + } + + protected void emitReport() { + emit(fd, EV_SYN, SYN_REPORT, 0); + } +}