com.sun.glass.ui.monocle.LinuxInputDevice Maven / Gradle / Ivy
/*
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.glass.ui.monocle;
import com.sun.glass.ui.Application;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.BitSet;
import java.util.Map;
/**
* A LinuxInputDevice listens for events on a Linux
* input device node, typically one of the files in /dev/input. When events are
* waiting to be processed on the device it notifies its listener on a thread
* provided by its runnable processor object.
*
* Event lines are accumulated in a buffer until an event "EV_SYN EV_SYN_REPORT
* 0" is received. At this point the listener is notified. The listener can then
* use the methods getEventType(), getEventCode() and getEventValue() to obtain
* the details of the current event line to process. nextEvent() and
* hasNextEvent() are used to iterate over pending events.
*
* To save on RAM and GC, event lines are not objects.
*/
class LinuxInputDevice implements Runnable, InputDevice {
private LinuxInputProcessor inputProcessor;
private ReadableByteChannel in;
private long fd = -1;
private File devNode;
private File sysPath;
private Map capabilities;
private Map absCaps;
private Map udevManifest;
private final ByteBuffer event;
private RunnableProcessor runnableProcessor;
private EventProcessor processor = new EventProcessor();
private final LinuxEventBuffer buffer;
private Map uevent;
private static LinuxSystem system = LinuxSystem.getLinuxSystem();
/**
* Create a new com.sun.glass.ui.monocle.input.LinuxInputDevice on the given
* input node.
*
* @param devNode The node on which to listen for input
* @param sysPath The sysfs path describing the device
* @throws IOException
*/
LinuxInputDevice(
File devNode,
File sysPath,
Map udevManifest) throws IOException {
this.buffer = new LinuxEventBuffer(LinuxArch.getBits());
this.event = ByteBuffer.allocateDirect(buffer.getEventSize());
this.devNode = devNode;
this.sysPath = sysPath;
this.udevManifest = udevManifest;
this.capabilities = SysFS.readCapabilities(sysPath);
fd = system.open(devNode.getPath(), LinuxSystem.O_RDONLY);
if (fd == -1) {
throw new IOException(system.getErrorMessage() + " on " + devNode);
}
this.absCaps = LinuxAbsoluteInputCapabilities.getCapabilities(
devNode, capabilities.get("abs"));
// attempt to grab the device. If the grab fails, keep going.
int EVIOCGRAB = system.IOW('E', 0x90, 4);
system.ioctl(fd, EVIOCGRAB, 1);
this.runnableProcessor = NativePlatformFactory.getNativePlatform()
.getRunnableProcessor();
this.uevent = SysFS.readUEvent(sysPath);
}
/**
* Create a new simulated LinuxInputDevice
*
* @param capabilities Simulated capabilities
* @param absCaps Simulated absolute axis capabilities
* @param in Channel f or simulated input events
* @param uevent Simulated uevent data
*/
LinuxInputDevice(
Map capabilities,
Map absCaps,
ReadableByteChannel in,
Map udevManifest,
Map uevent) {
this.buffer = new LinuxEventBuffer(32);
this.event = ByteBuffer.allocateDirect(buffer.getEventSize());
this.capabilities = capabilities;
this.absCaps = absCaps;
this.in = in;
this.udevManifest = udevManifest;
this.uevent = uevent;
this.runnableProcessor = NativePlatformFactory.getNativePlatform()
.getRunnableProcessor();
}
void setInputProcessor(LinuxInputProcessor inputProcessor) {
this.inputProcessor = inputProcessor;
}
private void readToEventBuffer() throws IOException {
if (in != null) {
in.read(event);
} else if (fd != -1) {
int position = event.position();
int bytesRead = (int) system.read(fd, event, position, event.limit());
if (bytesRead == -1) {
throw new IOException(system.getErrorMessage() + " on " + devNode);
} else {
event.position(position + bytesRead);
}
}
}
@Override
public void run() {
if (inputProcessor == null) {
System.err.println("Error: no input processor set on " + devNode);
return;
}
while (true) {
try {
readToEventBuffer();
if (event.position() == event.limit()) {
event.flip();
synchronized (buffer) {
if (buffer.put(event) && !processor.scheduled) {
runnableProcessor.invokeLater(processor);
processor.scheduled = true;
}
}
event.rewind();
}
} catch (IOException | InterruptedException e) {
// the device is disconnected
return;
}
}
}
/**
* The EventProcessor is used to notify listeners of pending events. It runs
* on the application thread.
*/
class EventProcessor implements Runnable {
boolean scheduled;
public void run() {
buffer.startIteration();
// Do not lock the buffer while processing events. We still want to be
// able to add incoming events to it.
try {
inputProcessor.processEvents(LinuxInputDevice.this);
} catch (RuntimeException e) {
Application.reportException(e);
}
synchronized (buffer) {
if (buffer.hasNextEvent()) {
// a new event came in after the call to processEvents
runnableProcessor.invokeLater(processor);
} else {
processor.scheduled = false;
}
buffer.compact();
}
}
}
LinuxEventBuffer getBuffer() {
return buffer;
}
/** Asks whether the device is quiet. "Quiet" means that the event
* reader is blocked waiting for events, the buffer is empty and the event
* processor is not scheduled. Called on the application thread.
*/
boolean isQuiet() {
synchronized (buffer) {
return !processor.scheduled && !buffer.hasData();
}
}
/**
* @return a string describing this input device
*/
public String toString() {
return devNode == null ? "Robot" : devNode.toString();
}
BitSet getCapability(String type) {
return capabilities.get(type);
}
LinuxAbsoluteInputCapabilities getAbsoluteInputCapabilities(int axis) {
return absCaps == null ? null : absCaps.get(axis);
}
String getProduct() {
return uevent.get("PRODUCT");
}
@Override
public boolean isTouch() {
return "1".equals(udevManifest.get("ID_INPUT_TOUCHSCREEN"))
|| "1".equals(udevManifest.get("ID_INPUT_TABLET"))
|| isTouchDeclaredAsMouse();
}
private boolean isTouchDeclaredAsMouse() {
if ("1".equals(udevManifest.get("ID_INPUT_MOUSE"))) {
BitSet rel = capabilities.get("rel");
if (rel == null || (!rel.get(LinuxInput.REL_X) && !rel.get(LinuxInput.REL_Y))) {
BitSet abs = capabilities.get("abs");
if (abs != null
&& (abs.get(LinuxInput.ABS_X) || abs.get(LinuxInput.ABS_MT_POSITION_X))
&& (abs.get(LinuxInput.ABS_Y) || abs.get(LinuxInput.ABS_MT_POSITION_Y))) {
return true;
}
}
}
return false;
}
@Override
public boolean isMultiTouch() {
if (isTouch()) {
BitSet abs = capabilities.get("abs");
if (abs == null) {
return false;
}
return abs.get(LinuxInput.ABS_MT_SLOT)
|| (abs.get(LinuxInput.ABS_MT_POSITION_X)
&& abs.get(LinuxInput.ABS_MT_POSITION_Y));
} else {
return false;
}
}
@Override
public boolean isRelative() {
return "1".equals(udevManifest.get("ID_INPUT_MOUSE"));
}
@Override
public boolean is5Way() {
BitSet key = capabilities.get("key");
if (key == null) {
return false;
}
// Make sure we have all arrow keys
for (int i = 0; i < LinuxKeyBits.KEYBITS_ARROWS.length; i++) {
if (!key.get(LinuxKeyBits.KEYBITS_ARROWS[i])) {
return false;
}
}
// and at least one select key
for (int i = 0; i < LinuxKeyBits.KEYBITS_SELECT.length; i++) {
if (key.get(LinuxKeyBits.KEYBITS_SELECT[i])) {
return true;
}
}
return false;
}
@Override
public boolean isFullKeyboard() {
BitSet key = capabilities.get("key");
if (key == null) {
return false;
}
// Make sure we have all alphanumeric keys
for (int i = 0; i < LinuxKeyBits.KEYBITS_PC.length; i++) {
if (!key.get(LinuxKeyBits.KEYBITS_PC[i])) {
return false;
}
}
// ...and the 5-way keys
return is5Way();
}
}