2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.linuxinput.internal.evdev4j;
15 import static org.openhab.binding.linuxinput.internal.evdev4j.Utils.combineFlags;
17 import java.io.Closeable;
18 import java.io.IOException;
19 import java.nio.channels.ClosedChannelException;
20 import java.nio.channels.SelectableChannel;
21 import java.nio.channels.SelectionKey;
22 import java.nio.channels.Selector;
23 import java.nio.channels.spi.SelectorProvider;
24 import java.text.MessageFormat;
25 import java.time.Instant;
26 import java.time.format.DateTimeFormatter;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.List;
30 import java.util.Optional;
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.openhab.binding.linuxinput.internal.evdev4j.jnr.EvdevLibrary;
35 import jnr.constants.platform.Errno;
36 import jnr.constants.platform.OpenFlags;
37 import jnr.enxio.channels.NativeDeviceChannel;
38 import jnr.enxio.channels.NativeFileSelectorProvider;
39 import jnr.ffi.byref.PointerByReference;
40 import jnr.posix.POSIX;
41 import jnr.posix.POSIXFactory;
44 * Classbased access to libevdev-input functionality.
46 * @author Thomas Weißschuh - Initial contribution
49 public class EvdevDevice implements Closeable {
50 private static final SelectorProvider SELECTOR_PROVIDER = NativeFileSelectorProvider.getInstance();
52 private final EvdevLibrary lib = EvdevLibrary.load();
53 private final POSIX posix = POSIXFactory.getNativePOSIX();
54 private final SelectableChannel channel;
55 private final EvdevLibrary.Handle handle;
57 public EvdevDevice(String path) throws IOException {
58 int fd = posix.open(path, combineFlags(OpenFlags.class, OpenFlags.O_RDONLY, OpenFlags.O_CLOEXEC), 0);
60 throw new LastErrorException(posix, posix.errno(), path);
63 EvdevLibrary.Handle newHandle = EvdevLibrary.makeHandle(lib);
64 PointerByReference ref = new PointerByReference();
65 int ret = lib.new_from_fd(fd, ref);
67 throw new LastErrorException(posix, -ret);
69 newHandle.useMemory(ref.getValue());
71 NativeDeviceChannel newChannel = new NativeDeviceChannel(SELECTOR_PROVIDER, fd, SelectionKey.OP_READ, true);
72 newChannel.configureBlocking(false);
76 private void grab(EvdevLibrary.GrabMode mode) {
77 int ret = lib.grab(handle, mode.getValue());
79 throw new LastErrorException(posix, -ret);
84 grab(EvdevLibrary.GrabMode.GRAB);
87 public void ungrab() {
88 grab(EvdevLibrary.GrabMode.UNGRAB);
92 public String toString() {
93 return MessageFormat.format("Evdev {0}|{1}|{2}", lib.get_name(handle), lib.get_phys(handle),
94 lib.get_uniq(handle));
97 public Optional<InputEvent> nextEvent() {
98 // EV_SYN/SYN_DROPPED handling?
99 EvdevLibrary.InputEvent event = new EvdevLibrary.InputEvent(jnr.ffi.Runtime.getRuntime(lib));
100 int ret = lib.next_event(handle, EvdevLibrary.ReadFlag.NORMAL, event);
101 if (ret == -Errno.EAGAIN.intValue()) {
102 return Optional.empty();
105 throw new LastErrorException(posix, -ret);
107 return Optional.of(new InputEvent(lib, Instant.ofEpochSecond(event.sec.get(), event.usec.get()),
108 event.type.get(), event.code.get(), event.value.get()));
111 public static Selector openSelector() throws IOException {
112 return SELECTOR_PROVIDER.openSelector();
115 public SelectionKey register(Selector selector) throws ClosedChannelException {
116 return channel.register(selector, SelectionKey.OP_READ);
120 public synchronized void close() throws IOException {
125 @NonNullByDefault({})
126 public String getName() {
127 return lib.get_name(handle);
130 public void setName(String name) {
131 lib.set_name(handle, name);
134 @NonNullByDefault({})
135 public String getPhys() {
136 return lib.get_phys(handle);
139 @NonNullByDefault({})
140 public String getUniq() {
141 return lib.get_uniq(handle);
144 public int getProdutId() {
145 return lib.get_id_product(handle);
148 public int getVendorId() {
149 return lib.get_id_vendor(handle);
152 public int getBusId() {
153 return lib.get_id_bustype(handle);
156 public Optional<EvdevLibrary.BusType> getBusType() {
157 return EvdevLibrary.BusType.fromInt(getBusId());
160 public int getVersionId() {
161 return lib.get_id_version(handle);
164 public int getDriverVersion() {
165 return lib.get_driver_version(handle);
168 public boolean has(EvdevLibrary.Type type) {
169 return lib.has_event_type(handle, type.intValue());
172 public boolean has(EvdevLibrary.Type type, int code) {
173 return lib.has_event_code(handle, type.intValue(), code);
176 public void enable(EvdevLibrary.Type type) {
177 int result = lib.enable_event_type(handle, type.intValue());
179 throw new FailedOperationException();
183 public void enable(EvdevLibrary.Type type, int code) {
184 int result = lib.enable_event_code(handle, type.intValue(), code);
186 throw new FailedOperationException();
190 public Collection<Key> enumerateKeys() {
192 int maxKey = lib.event_type_get_max(EvdevLibrary.Type.KEY.intValue());
193 List<Key> result = new ArrayList<>();
194 for (int i = minKey; i <= maxKey; i++) {
195 if (has(EvdevLibrary.Type.KEY, i)) {
196 result.add(new Key(lib, i));
202 public static class Key {
203 private final EvdevLibrary lib;
204 private final int code;
206 private Key(EvdevLibrary lib, int code) {
211 public int getCode() {
215 public String getName() {
216 return lib.event_code_get_name(EvdevLibrary.Type.KEY.intValue(), code);
220 public String toString() {
221 return String.valueOf(code);
225 public static class InputEvent {
226 private EvdevLibrary lib;
228 private final Instant time;
229 private final int type;
230 private final int code;
231 private final int value;
233 private InputEvent(EvdevLibrary lib, Instant time, int type, int code, int value) {
241 public Instant getTime() {
245 public int getType() {
249 public EvdevLibrary.Type type() {
250 return EvdevLibrary.Type.fromInt(type)
251 .orElseThrow(() -> new IllegalStateException("Could not find 'Type' for value " + type));
254 public Optional<String> typeName() {
255 return Optional.ofNullable(lib.event_type_get_name(type));
258 public int getCode() {
262 public Optional<String> codeName() {
263 return Optional.ofNullable(lib.event_code_get_name(type, code));
266 public int getValue() {
270 public Optional<String> valueName() {
271 return Optional.ofNullable(lib.event_value_get_name(type, code, value));
275 public String toString() {
276 return MessageFormat.format("{0}: {1}/{2}/{3}", DateTimeFormatter.ISO_INSTANT.format(time),
277 typeName().orElse(String.valueOf(type)), codeName().orElse(String.valueOf(code)),
278 valueName().orElse(String.valueOf(value)));
282 public static class FailedOperationException extends RuntimeException {
283 private static final long serialVersionUID = -8556632931670798678L;