1 /*
2  * Copyright (c) 2003 Sun Microsystems, Inc.  All Rights Reserved.
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5  *
6  * - Redistribution of source code must retain the above copyright notice,
7  *   this list of conditions and the following disclaimer.
8  *
9  * - Redistribution in binary form must reproduce the above copyright notice,
10  *   this list of conditions and the following disclaimer in the documentation
11  *   and/or other materails provided with the distribution.
12  *
13  * Neither the name Sun Microsystems, Inc. or the names of the contributors
14  * may be used to endorse or promote products derived from this software
15  * without specific prior written permission.
16  *
17  * This software is provided "AS IS," without a warranty of any kind.
18  * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
19  * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
20  * NON-INFRINGEMEN, ARE HEREBY EXCLUDED.  SUN MICROSYSTEMS, INC. ("SUN") AND
21  * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS
22  * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS
23  * DERIVATIVES.  IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
24  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
25  * INCIDENTAL OR PUNITIVE DAMAGES.  HOWEVER CAUSED AND REGARDLESS OF THE THEORY
26  * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE,
27  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
28  *
29  * You acknowledge that this software is not designed or intended for us in
30  * the design, construction, operation or maintenance of any nuclear facility
31  *
32  */
33 package net.java.games.input;
34 
35 import java.io.IOException;
36 import java.util.List;
37 import java.util.ArrayList;
38 import java.util.Map;
39 import java.util.Iterator;
40 import java.util.logging.Logger;
41 
42 /** OSX HIDManager implementation
43 * @author elias
44 * @author gregorypierce
45 * @version 1.0
46 */
47 final class OSXHIDDevice {
48 	private final static Logger log = Logger.getLogger(OSXHIDDevice.class.getName());
49 	private final static int AXIS_DEFAULT_MIN_VALUE = 0;
50 	private final static int AXIS_DEFAULT_MAX_VALUE = 64*1024;
51 
52 	private final static String kIOHIDTransportKey                  = "Transport";
53 	private final static String kIOHIDVendorIDKey                   = "VendorID";
54 	private final static String kIOHIDVendorIDSourceKey             = "VendorIDSource";
55 	private final static String kIOHIDProductIDKey                  = "ProductID";
56 	private final static String kIOHIDVersionNumberKey              = "VersionNumber";
57 	private final static String kIOHIDManufacturerKey               = "Manufacturer";
58 	private final static String kIOHIDProductKey                    = "Product";
59 	private final static String kIOHIDSerialNumberKey               = "SerialNumber";
60 	private final static String kIOHIDCountryCodeKey                = "CountryCode";
61 	private final static String kIOHIDLocationIDKey                 = "LocationID";
62 	private final static String kIOHIDDeviceUsageKey                = "DeviceUsage";
63 	private final static String kIOHIDDeviceUsagePageKey            = "DeviceUsagePage";
64 	private final static String kIOHIDDeviceUsagePairsKey           = "DeviceUsagePairs";
65 	private final static String kIOHIDPrimaryUsageKey               = "PrimaryUsage";
66 	private final static String kIOHIDPrimaryUsagePageKey           = "PrimaryUsagePage";
67 	private final static String kIOHIDMaxInputReportSizeKey     = "MaxInputReportSize";
68 	private final static String kIOHIDMaxOutputReportSizeKey        = "MaxOutputReportSize";
69 	private final static String kIOHIDMaxFeatureReportSizeKey       = "MaxFeatureReportSize";
70 
71 	private final static String kIOHIDElementKey                    = "Elements";
72 
73 	private final static String kIOHIDElementCookieKey              = "ElementCookie";
74 	private final static String kIOHIDElementTypeKey                = "Type";
75 	private final static String kIOHIDElementCollectionTypeKey      = "CollectionType";
76 	private final static String kIOHIDElementUsageKey               = "Usage";
77 	private final static String kIOHIDElementUsagePageKey           = "UsagePage";
78 	private final static String kIOHIDElementMinKey                 = "Min";
79 	private final static String kIOHIDElementMaxKey                 = "Max";
80 	private final static String kIOHIDElementScaledMinKey           = "ScaledMin";
81 	private final static String kIOHIDElementScaledMaxKey           = "ScaledMax";
82 	private final static String kIOHIDElementSizeKey                = "Size";
83 	private final static String kIOHIDElementReportSizeKey          = "ReportSize";
84 	private final static String kIOHIDElementReportCountKey         = "ReportCount";
85 	private final static String kIOHIDElementReportIDKey            = "ReportID";
86 	private final static String kIOHIDElementIsArrayKey             = "IsArray";
87 	private final static String kIOHIDElementIsRelativeKey          = "IsRelative";
88 	private final static String kIOHIDElementIsWrappingKey          = "IsWrapping";
89 	private final static String kIOHIDElementIsNonLinearKey         = "IsNonLinear";
90 	private final static String kIOHIDElementHasPreferredStateKey   = "HasPreferredState";
91 	private final static String kIOHIDElementHasNullStateKey        = "HasNullState";
92 	private final static String kIOHIDElementUnitKey                = "Unit";
93 	private final static String kIOHIDElementUnitExponentKey        = "UnitExponent";
94 	private final static String kIOHIDElementNameKey                = "Name";
95 	private final static String kIOHIDElementValueLocationKey       = "ValueLocation";
96 	private final static String kIOHIDElementDuplicateIndexKey      = "DuplicateIndex";
97 	private final static String kIOHIDElementParentCollectionKey    = "ParentCollection";
98 
99 	private final long device_address;
100 	private final long device_interface_address;
101 	private final Map<String,?> properties;
102 
103 	private boolean released;
104 
OSXHIDDevice(long device_address, long device_interface_address)105 	public OSXHIDDevice(long device_address, long device_interface_address) throws IOException {
106 		this.device_address = device_address;
107 		this.device_interface_address = device_interface_address;
108 		this.properties = getDeviceProperties();
109 		open();
110 	}
111 
getPortType()112 	public final Controller.PortType getPortType() {
113 		String transport = (String)properties.get(kIOHIDTransportKey);
114 		if (transport == null)
115 			return Controller.PortType.UNKNOWN;
116 		if (transport.equals("USB")) {
117 			return Controller.PortType.USB;
118 		} else {
119 			return Controller.PortType.UNKNOWN;
120 		}
121 	}
122 
getProductName()123 	public final String getProductName() {
124 		return (String)properties.get(kIOHIDProductKey);
125 	}
126 
createElementFromElementProperties(Map<String,?> element_properties)127 	private final OSXHIDElement createElementFromElementProperties(Map<String,?> element_properties) {
128 	/*	long size = getLongFromProperties(element_properties, kIOHIDElementSizeKey);
129 		// ignore elements that can't fit into the 32 bit value field of a hid event
130 		if (size > 32)
131 			return null;*/
132 		long element_cookie = getLongFromProperties(element_properties, kIOHIDElementCookieKey);
133 		int element_type_id = getIntFromProperties(element_properties, kIOHIDElementTypeKey);
134 		ElementType element_type = ElementType.map(element_type_id);
135 		int min = (int)getLongFromProperties(element_properties, kIOHIDElementMinKey, AXIS_DEFAULT_MIN_VALUE);
136 		int max = (int)getLongFromProperties(element_properties, kIOHIDElementMaxKey, AXIS_DEFAULT_MAX_VALUE);
137 /*		long scaled_min = getLongFromProperties(element_properties, kIOHIDElementScaledMinKey, Long.MIN_VALUE);
138 		long scaled_max = getLongFromProperties(element_properties, kIOHIDElementScaledMaxKey, Long.MAX_VALUE);*/
139 		UsagePair device_usage_pair = getUsagePair();
140 		boolean default_relative = device_usage_pair != null && (device_usage_pair.getUsage() == GenericDesktopUsage.POINTER || device_usage_pair.getUsage() == GenericDesktopUsage.MOUSE);
141 
142 		boolean is_relative = getBooleanFromProperties(element_properties, kIOHIDElementIsRelativeKey, default_relative);
143 /*		boolean is_wrapping = getBooleanFromProperties(element_properties, kIOHIDElementIsWrappingKey);
144 		boolean is_non_linear = getBooleanFromProperties(element_properties, kIOHIDElementIsNonLinearKey);
145 		boolean has_preferred_state = getBooleanFromProperties(element_properties, kIOHIDElementHasPreferredStateKey);
146 		boolean has_null_state = getBooleanFromProperties(element_properties, kIOHIDElementHasNullStateKey);*/
147 		int usage = getIntFromProperties(element_properties, kIOHIDElementUsageKey);
148 		int usage_page = getIntFromProperties(element_properties, kIOHIDElementUsagePageKey);
149 		UsagePair usage_pair = createUsagePair(usage_page, usage);
150 		if (usage_pair == null || (element_type != ElementType.INPUT_MISC && element_type != ElementType.INPUT_BUTTON && element_type != ElementType.INPUT_AXIS)) {
151 			//log.info("element_type = 0x" + element_type + " | usage = " + usage + " | usage_page = " + usage_page);
152 			return null;
153 		} else {
154 			return new OSXHIDElement(this, usage_pair, element_cookie, element_type, min, max, is_relative);
155 		}
156 	}
157 
158     @SuppressWarnings("unchecked")
addElements(List<OSXHIDElement> elements, Map<String,?> properties)159     private final void addElements(List<OSXHIDElement> elements, Map<String,?> properties) {
160 		Object[] elements_properties = (Object[])properties.get(kIOHIDElementKey);
161 		if (elements_properties == null)
162 			return;
163 		for (int i = 0; i < elements_properties.length; i++) {
164 			Map<String,?> element_properties = (Map<String,?>)elements_properties[i];
165 			OSXHIDElement element = createElementFromElementProperties(element_properties);
166 			if (element != null) {
167 				elements.add(element);
168 			}
169 			addElements(elements, element_properties);
170 		}
171 	}
172 
getElements()173 	public final List<OSXHIDElement> getElements() {
174 		List<OSXHIDElement> elements = new ArrayList<>();
175 		addElements(elements, properties);
176 		return elements;
177 	}
178 
getLongFromProperties(Map<String,?> properties, String key, long default_value)179 	private final static long getLongFromProperties(Map<String,?> properties, String key, long default_value) {
180 		Long long_obj = (Long)properties.get(key);
181 		if (long_obj == null)
182 			return default_value;
183 		return long_obj.longValue();
184 	}
185 
getBooleanFromProperties(Map<String,?> properties, String key, boolean default_value)186 	private final static boolean getBooleanFromProperties(Map<String,?> properties, String key, boolean default_value) {
187 		return getLongFromProperties(properties, key, default_value ? 1 : 0) != 0;
188 	}
189 
getIntFromProperties(Map<String,?> properties, String key)190 	private final static int getIntFromProperties(Map<String,?> properties, String key) {
191 		return (int)getLongFromProperties(properties, key);
192 	}
193 
getLongFromProperties(Map<String,?> properties, String key)194 	private final static long getLongFromProperties(Map<String,?> properties, String key) {
195 		Long long_obj = (Long)properties.get(key);
196 		return long_obj.longValue();
197 	}
198 
createUsagePair(int usage_page_id, int usage_id)199 	private final static UsagePair createUsagePair(int usage_page_id, int usage_id) {
200 		UsagePage usage_page = UsagePage.map(usage_page_id);
201 		if (usage_page != null) {
202 			Usage usage = usage_page.mapUsage(usage_id);
203 			if (usage != null)
204 				return new UsagePair(usage_page, usage);
205 		}
206 		return null;
207 	}
208 
getUsagePair()209 	public final UsagePair getUsagePair() {
210 		int usage_page_id = getIntFromProperties(properties, kIOHIDPrimaryUsagePageKey);
211 		int usage_id = getIntFromProperties(properties, kIOHIDPrimaryUsageKey);
212 		return createUsagePair(usage_page_id, usage_id);
213 	}
214 
dumpProperties()215 	private final void dumpProperties() {
216 		log.info(toString());
217 		dumpMap("", properties);
218 	}
219 
dumpArray(String prefix, Object[] array)220 	private final static void dumpArray(String prefix, Object[] array) {
221 		log.info(prefix + "{");
222 		for (int i = 0; i < array.length; i++) {
223 			dumpObject(prefix + "\t", array[i]);
224 			log.info(prefix + ",");
225 		}
226 		log.info(prefix + "}");
227 	}
228 
dumpMap(String prefix, Map<String,?> map)229 	private final static void dumpMap(String prefix, Map<String,?> map) {
230 		Iterator<String> keys = map.keySet().iterator();
231 		while (keys.hasNext()) {
232 			Object key = keys.next();
233 			Object value = map.get(key);
234 			dumpObject(prefix, key);
235 			dumpObject(prefix + "\t", value);
236 		}
237 	}
238 
239 	@SuppressWarnings("unchecked")
dumpObject(String prefix, Object obj)240 	private final static void dumpObject(String prefix, Object obj) {
241 		if (obj instanceof Long) {
242 			Long l = (Long)obj;
243 			log.info(prefix + "0x" + Long.toHexString(l.longValue()));
244 		} else if (obj instanceof Map)
245 			dumpMap(prefix, (Map<String,?>)obj);
246 		else if (obj.getClass().isArray())
247 			dumpArray(prefix, (Object[])obj);
248 		else
249 			log.info(prefix + obj);
250 	}
251 
getDeviceProperties()252 	private final Map<String,?> getDeviceProperties() throws IOException {
253 		return nGetDeviceProperties(device_address);
254 	}
nGetDeviceProperties(long device_address)255 	private final static native Map<String,?> nGetDeviceProperties(long device_address) throws IOException;
256 
release()257 	public final synchronized void release() throws IOException {
258 		try {
259 			close();
260 		} finally {
261 			released = true;
262 			nReleaseDevice(device_address, device_interface_address);
263 		}
264 	}
nReleaseDevice(long device_address, long device_interface_address)265 	private final static native void nReleaseDevice(long device_address, long device_interface_address);
266 
getElementValue(long element_cookie, OSXEvent event)267 	public final synchronized void getElementValue(long element_cookie, OSXEvent event) throws IOException {
268 		checkReleased();
269 		nGetElementValue(device_interface_address, element_cookie, event);
270 	}
nGetElementValue(long device_interface_address, long element_cookie, OSXEvent event)271 	private final static native void nGetElementValue(long device_interface_address, long element_cookie, OSXEvent event) throws IOException;
272 
createQueue(int queue_depth)273 	public final synchronized OSXHIDQueue createQueue(int queue_depth) throws IOException {
274 		checkReleased();
275 		long queue_address = nCreateQueue(device_interface_address);
276 		return new OSXHIDQueue(queue_address, queue_depth);
277 	}
nCreateQueue(long device_interface_address)278 	private final static native long nCreateQueue(long device_interface_address) throws IOException;
279 
open()280 	private final void open() throws IOException {
281 		nOpen(device_interface_address);
282 	}
nOpen(long device_interface_address)283 	private final static native void nOpen(long device_interface_address) throws IOException;
284 
close()285 	private final void close() throws IOException {
286 		nClose(device_interface_address);
287 	}
nClose(long device_interface_address)288 	private final static native void nClose(long device_interface_address) throws IOException;
289 
checkReleased()290 	private final void checkReleased() throws IOException {
291 		if (released)
292 			throw new IOException();
293 	}
294 }
295