]> git.basschouten.com Git - openhab-addons.git/blob
fcae0459625de9aec52fa566831553709896bed5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.linuxinput.internal.evdev4j;
14
15 import static org.openhab.binding.linuxinput.internal.evdev4j.Utils.combineFlags;
16
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;
31
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.openhab.binding.linuxinput.internal.evdev4j.jnr.EvdevLibrary;
34
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;
42
43 /**
44  * Classbased access to libevdev-input functionality.
45  *
46  * @author Thomas Weißschuh - Initial contribution
47  */
48 @NonNullByDefault
49 public class EvdevDevice implements Closeable {
50     private static final SelectorProvider SELECTOR_PROVIDER = NativeFileSelectorProvider.getInstance();
51
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;
56
57     public EvdevDevice(String path) throws IOException {
58         int fd = posix.open(path, combineFlags(OpenFlags.class, OpenFlags.O_RDONLY, OpenFlags.O_CLOEXEC), 0);
59         if (fd == -1) {
60             throw new LastErrorException(posix, posix.errno(), path);
61         }
62
63         EvdevLibrary.Handle newHandle = EvdevLibrary.makeHandle(lib);
64         PointerByReference ref = new PointerByReference();
65         int ret = lib.new_from_fd(fd, ref);
66         if (ret != 0) {
67             throw new LastErrorException(posix, -ret);
68         }
69         newHandle.useMemory(ref.getValue());
70         handle = newHandle;
71         NativeDeviceChannel newChannel = new NativeDeviceChannel(SELECTOR_PROVIDER, fd, SelectionKey.OP_READ, true);
72         newChannel.configureBlocking(false);
73         channel = newChannel;
74     }
75
76     private void grab(EvdevLibrary.GrabMode mode) {
77         int ret = lib.grab(handle, mode.getValue());
78         if (ret != 0) {
79             throw new LastErrorException(posix, -ret);
80         }
81     }
82
83     public void grab() {
84         grab(EvdevLibrary.GrabMode.GRAB);
85     }
86
87     public void ungrab() {
88         grab(EvdevLibrary.GrabMode.UNGRAB);
89     }
90
91     @Override
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));
95     }
96
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();
103         }
104         if (ret < 0) {
105             throw new LastErrorException(posix, -ret);
106         }
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()));
109     }
110
111     public static Selector openSelector() throws IOException {
112         return SELECTOR_PROVIDER.openSelector();
113     }
114
115     public SelectionKey register(Selector selector) throws ClosedChannelException {
116         return channel.register(selector, SelectionKey.OP_READ);
117     }
118
119     @Override
120     public synchronized void close() throws IOException {
121         lib.free(handle);
122         channel.close();
123     }
124
125     @NonNullByDefault({})
126     public String getName() {
127         return lib.get_name(handle);
128     }
129
130     public void setName(String name) {
131         lib.set_name(handle, name);
132     }
133
134     @NonNullByDefault({})
135     public String getPhys() {
136         return lib.get_phys(handle);
137     }
138
139     @NonNullByDefault({})
140     public String getUniq() {
141         return lib.get_uniq(handle);
142     }
143
144     public int getProdutId() {
145         return lib.get_id_product(handle);
146     }
147
148     public int getVendorId() {
149         return lib.get_id_vendor(handle);
150     }
151
152     public int getBusId() {
153         return lib.get_id_bustype(handle);
154     }
155
156     public Optional<EvdevLibrary.BusType> getBusType() {
157         return EvdevLibrary.BusType.fromInt(getBusId());
158     }
159
160     public int getVersionId() {
161         return lib.get_id_version(handle);
162     }
163
164     public int getDriverVersion() {
165         return lib.get_driver_version(handle);
166     }
167
168     public boolean has(EvdevLibrary.Type type) {
169         return lib.has_event_type(handle, type.intValue());
170     }
171
172     public boolean has(EvdevLibrary.Type type, int code) {
173         return lib.has_event_code(handle, type.intValue(), code);
174     }
175
176     public void enable(EvdevLibrary.Type type) {
177         int result = lib.enable_event_type(handle, type.intValue());
178         if (result != 0) {
179             throw new FailedOperationException();
180         }
181     }
182
183     public void enable(EvdevLibrary.Type type, int code) {
184         int result = lib.enable_event_code(handle, type.intValue(), code);
185         if (result != 0) {
186             throw new FailedOperationException();
187         }
188     }
189
190     public Collection<Key> enumerateKeys() {
191         int minKey = 0;
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));
197             }
198         }
199         return result;
200     }
201
202     public static class Key {
203         private final EvdevLibrary lib;
204         private final int code;
205
206         private Key(EvdevLibrary lib, int code) {
207             this.lib = lib;
208             this.code = code;
209         }
210
211         public int getCode() {
212             return code;
213         }
214
215         public String getName() {
216             return lib.event_code_get_name(EvdevLibrary.Type.KEY.intValue(), code);
217         }
218
219         @Override
220         public String toString() {
221             return String.valueOf(code);
222         }
223     }
224
225     public static class InputEvent {
226         private EvdevLibrary lib;
227
228         private final Instant time;
229         private final int type;
230         private final int code;
231         private final int value;
232
233         private InputEvent(EvdevLibrary lib, Instant time, int type, int code, int value) {
234             this.lib = lib;
235             this.time = time;
236             this.type = type;
237             this.code = code;
238             this.value = value;
239         }
240
241         public Instant getTime() {
242             return time;
243         }
244
245         public int getType() {
246             return type;
247         }
248
249         public EvdevLibrary.Type type() {
250             return EvdevLibrary.Type.fromInt(type)
251                     .orElseThrow(() -> new IllegalStateException("Could not find 'Type' for value " + type));
252         }
253
254         public Optional<String> typeName() {
255             return Optional.ofNullable(lib.event_type_get_name(type));
256         }
257
258         public int getCode() {
259             return code;
260         }
261
262         public Optional<String> codeName() {
263             return Optional.ofNullable(lib.event_code_get_name(type, code));
264         }
265
266         public int getValue() {
267             return value;
268         }
269
270         public Optional<String> valueName() {
271             return Optional.ofNullable(lib.event_value_get_name(type, code, value));
272         }
273
274         @Override
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)));
279         }
280     }
281
282     public static class FailedOperationException extends RuntimeException {
283         private static final long serialVersionUID = -8556632931670798678L;
284     }
285 }