From 1590094c5e4ca09ed710a00403b8a20bf0358222 Mon Sep 17 00:00:00 2001 From: Matteo Hausner Date: Thu, 2 Jan 2025 18:16:51 +0100 Subject: [PATCH] EvdevDriver: ensure release of input event file descriptors --- .../input/driver/EvdevDriver.java | 133 ++++++++++-------- 1 file changed, 73 insertions(+), 60 deletions(-) diff --git a/src/main/java/de/bwravencl/controllerbuddy/input/driver/EvdevDriver.java b/src/main/java/de/bwravencl/controllerbuddy/input/driver/EvdevDriver.java index 292c1d1a..503d9058 100644 --- a/src/main/java/de/bwravencl/controllerbuddy/input/driver/EvdevDriver.java +++ b/src/main/java/de/bwravencl/controllerbuddy/input/driver/EvdevDriver.java @@ -31,7 +31,6 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Stream; import uk.co.bithatch.linuxio.CLib; import uk.co.bithatch.linuxio.EventCode; import uk.co.bithatch.linuxio.Input.Macros; @@ -196,76 +195,90 @@ private static boolean testBit(final short bit, final NativeLong[] array) { @Override public Optional getIfAvailable(final Input input, final List presentControllers, final ControllerInfo selectedController) { - if (Main.isLinux && input.isHapticFeedback()) { - final var inputDir = new File("/dev/input/"); - final var allEventFiles = inputDir - .listFiles((final var _, final var name) -> name.matches("event(\\d+)")); + if (!Main.isLinux || !input.isHapticFeedback()) { + return Optional.empty(); + } + + final var inputDir = new File("/dev/input/"); + final var allEventFiles = inputDir.listFiles((final var _, final var name) -> name.matches("event(\\d+)")); + + if (allEventFiles == null) { + return Optional.empty(); + } - if (allEventFiles == null) { - return Optional.empty(); + final var evdevInfos = Arrays.stream(allEventFiles).mapMulti((eventFile, downstream) -> { + final var fd = CLib.INSTANCE.open(eventFile.getAbsolutePath(), CLib.O_RDWR | CLib.O_NONBLOCK); + if (fd == -1) { + log.log(Level.WARNING, "Could not open device: " + eventFile.getAbsolutePath()); + return; } - final var evdevInfos = Arrays.stream(allEventFiles).flatMap(eventFile -> { - final var fd = CLib.INSTANCE.open(eventFile.getAbsolutePath(), CLib.O_RDWR | CLib.O_NONBLOCK); - try { - if (fd != -1) { - final var inputId = new input_id(); - if (CLib.INSTANCE.ioctl(fd, EVIOCGID, inputId) == 0) { - final var bustypeUnsigned = Short.toUnsignedInt(inputId.bustype); - final var vendorUnsigned = Short.toUnsignedInt(inputId.vendor); - final var productUnsigned = Short.toUnsignedInt(inputId.product); - final var versionUnsigned = Short.toUnsignedInt(inputId.version); - - String guid = null; - if (vendorUnsigned != 0 && productUnsigned != 0 && versionUnsigned != 0) { - guid = "%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000".formatted( - bustypeUnsigned & 0xff, bustypeUnsigned >> 8, vendorUnsigned & 0xff, - vendorUnsigned >> 8, productUnsigned & 0xff, productUnsigned >> 8, - versionUnsigned & 0xff, versionUnsigned >> 8); - } else { - final var nameBytes = new byte[256]; - if (CLib.INSTANCE.ioctl(fd, - uk.co.bithatch.linuxio.Input.Macros.EVIOCGNAME(nameBytes.length), - nameBytes) != 0) { - guid = "%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00".formatted( - bustypeUnsigned & 0xff, bustypeUnsigned >> 8, nameBytes[0], - nameBytes[1], nameBytes[2], nameBytes[3], nameBytes[4], nameBytes[5], - nameBytes[6], nameBytes[7], nameBytes[8], nameBytes[9], nameBytes[10]); - } - } + var closeFileDescriptor = true; + try { + final var inputId = new input_id(); + if (CLib.INSTANCE.ioctl(fd, EVIOCGID, inputId) == 0) { + final var bustypeUnsigned = Short.toUnsignedInt(inputId.bustype); + final var vendorUnsigned = Short.toUnsignedInt(inputId.vendor); + final var productUnsigned = Short.toUnsignedInt(inputId.product); + final var versionUnsigned = Short.toUnsignedInt(inputId.version); + + String guid = null; + if (vendorUnsigned != 0 && productUnsigned != 0 && versionUnsigned != 0) { + guid = "%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000".formatted(bustypeUnsigned & 0xff, + bustypeUnsigned >> 8, vendorUnsigned & 0xff, vendorUnsigned >> 8, + productUnsigned & 0xff, productUnsigned >> 8, versionUnsigned & 0xff, + versionUnsigned >> 8); + } else { + final var nameBytes = new byte[256]; + if (CLib.INSTANCE.ioctl(fd, + uk.co.bithatch.linuxio.Input.Macros.EVIOCGNAME(nameBytes.length), nameBytes) != 0) { + guid = "%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00".formatted( + bustypeUnsigned & 0xff, bustypeUnsigned >> 8, nameBytes[0], nameBytes[1], + nameBytes[2], nameBytes[3], nameBytes[4], nameBytes[5], nameBytes[6], + nameBytes[7], nameBytes[8], nameBytes[9], nameBytes[10]); + } + } - if (selectedController.guid().equals(guid)) { - final var ffFeatures = new NativeLong[FF_MAX]; - if (CLib.INSTANCE.ioctl(fd, Macros.EVIOCGBIT(EventCode.Type.EV_FF.code(), - ffFeatures.length * Character.BYTES), ffFeatures) != -1) { - if (testBit(FF_RUMBLE, ffFeatures)) { - return Stream.of(new EvdevInfo(fd, testBit(FF_GAIN, ffFeatures))); - } - } + if (selectedController.guid().equals(guid)) { + final var ffFeatures = new NativeLong[FF_MAX]; + if (CLib.INSTANCE.ioctl(fd, + Macros.EVIOCGBIT(EventCode.Type.EV_FF.code(), ffFeatures.length * Character.BYTES), + ffFeatures) != -1) { + if (testBit(FF_RUMBLE, ffFeatures)) { + downstream.accept(new EvdevInfo(fd, testBit(FF_GAIN, ffFeatures))); + closeFileDescriptor = false; } } } - } catch (final Throwable t) { - log.log(Level.WARNING, t.getMessage(), t); } - - closeFileDescriptor(fd); - return Stream.empty(); - }).toList(); - - if (evdevInfos.isEmpty()) { - return Optional.empty(); - } - - if (evdevInfos.size() > 1) { - log.log(Level.WARNING, "Found more than one controller with GUID '" + selectedController.guid() - + "' - evdev driver disabled"); + } catch (final Throwable t) { + log.log(Level.WARNING, t.getMessage(), t); + } finally { + if (closeFileDescriptor) { + closeFileDescriptor(fd); + } } + }).toList(); + if (!evdevInfos.isEmpty()) { + var closeFileDescriptors = true; try { - return Optional.of(new EvdevDriver(input, selectedController, evdevInfos.getFirst())); - } catch (final IOException e) { - log.log(Level.SEVERE, e.getMessage(), e); + if (evdevInfos.size() > 1) { + log.log(Level.WARNING, "Found more than one controller with GUID '" + selectedController.guid() + + "' - evdev driver disabled"); + return Optional.empty(); + } + + final Optional optionalEvdevDriver = Optional + .of(new EvdevDriver(input, selectedController, evdevInfos.getFirst())); + closeFileDescriptors = false; + return optionalEvdevDriver; + } catch (final Throwable t) { + log.log(Level.SEVERE, t.getMessage(), t); + } finally { + if (closeFileDescriptors) { + evdevInfos.forEach(evdevInfo -> closeFileDescriptor(evdevInfo.fd)); + } } }