1 /*
2  * %W% %E%
3  *
4  * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */
7 /*****************************************************************************
8  * Copyright (c) 2003 Sun Microsystems, Inc.  All Rights Reserved.
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions are met:
11  *
12  * - Redistribution of source code must retain the above copyright notice,
13  *   this list of conditions and the following disclaimer.
14  *
15  * - Redistribution in binary form must reproduce the above copyright notice,
16  *   this list of conditions and the following disclaimer in the documentation
17  *   and/or other materails provided with the distribution.
18  *
19  * Neither the name Sun Microsystems, Inc. or the names of the contributors
20  * may be used to endorse or promote products derived from this software
21  * without specific prior written permission.
22  *
23  * This software is provided "AS IS," without a warranty of any kind.
24  * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
25  * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
26  * NON-INFRINGEMEN, ARE HEREBY EXCLUDED.  SUN MICROSYSTEMS, INC. ("SUN") AND
27  * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS
28  * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS
29  * DERIVATIVES.  IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
30  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
31  * INCIDENTAL OR PUNITIVE DAMAGES.  HOWEVER CAUSED AND REGARDLESS OF THE THEORY
32  * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE,
33  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
34  *
35  * You acknowledge that this software is not designed or intended for us in
36  * the design, construction, operation or maintenance of any nuclear facility
37  *
38  *****************************************************************************/
39 package net.java.games.input;
40 
41 import java.security.AccessController;
42 import java.security.PrivilegedAction;
43 import java.util.List;
44 import java.util.ArrayList;
45 import java.io.File;
46 import java.io.IOException;
47 
48 import net.java.games.util.plugins.Plugin;
49 
50 /** DirectInput implementation of controller environment
51  * @author martak
52  * @author elias
53  * @version 1.0
54  */
55 public final class RawInputEnvironmentPlugin extends ControllerEnvironment implements Plugin {
56 
57 	private static boolean supported = false;
58 
59 	/**
60 	 * Static utility method for loading native libraries.
61 	 * It will try to load from either the path given by
62 	 * the net.java.games.input.librarypath property
63 	 * or through System.loadLibrary().
64 	 *
65 	 */
loadLibrary(final String lib_name)66 	static void loadLibrary(final String lib_name) {
67 		AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
68 					    try {
69     						String lib_path = System.getProperty("net.java.games.input.librarypath");
70     						if (lib_path != null)
71     							System.load(lib_path + File.separator + System.mapLibraryName(lib_name));
72     						else
73     							System.loadLibrary(lib_name);
74 					    } catch (UnsatisfiedLinkError e) {
75 					        e.printStackTrace();
76 					        supported = false;
77 					    }
78 						return null;
79 				});
80 	}
81 
getPrivilegedProperty(final String property)82 	static String getPrivilegedProperty(final String property) {
83 	       return AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty(property));
84 		}
85 
86 
getPrivilegedProperty(final String property, final String default_value)87 	static String getPrivilegedProperty(final String property, final String default_value) {
88        return AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty(property, default_value));
89 	}
90 
91 	static {
92 		String osName = getPrivilegedProperty("os.name", "").trim();
93 		if(osName.startsWith("Windows")) {
94 			supported = true;
95 			if("x86".equals(getPrivilegedProperty("os.arch"))) {
96 				loadLibrary("jinput-raw");
97 			} else {
98 				loadLibrary("jinput-raw_64");
99 			}
100 		}
101 	}
102 
103     private final Controller[] controllers;
104 
105 	/** Creates new DirectInputEnvironment */
RawInputEnvironmentPlugin()106 	public RawInputEnvironmentPlugin() {
107 		RawInputEventQueue queue;
108 		Controller[] controllers = new Controller[]{};
109 		if(isSupported()) {
110 			try {
111 				queue = new RawInputEventQueue();
112 				controllers = enumControllers(queue);
113 			} catch (IOException e) {
114 				log("Failed to enumerate devices: " + e.getMessage());
115 			}
116 		}
117 		this.controllers = controllers;
118 	}
119 
getControllers()120 	public final Controller[] getControllers() {
121 		return controllers;
122 	}
123 
lookupSetupAPIDevice(String device_name, List<SetupAPIDevice> setupapi_devices)124 	private final static SetupAPIDevice lookupSetupAPIDevice(String device_name, List<SetupAPIDevice> setupapi_devices) {
125 		/* First, replace # with / in the device name, since that
126 		 * seems to be the format in raw input device name
127 		 */
128 		device_name = device_name.replaceAll("#", "\\\\").toUpperCase();
129 		for (int i = 0; i < setupapi_devices.size(); i++) {
130 			SetupAPIDevice device = setupapi_devices.get(i);
131 			if (device_name.contains(device.getInstanceId().toUpperCase()))
132 				return device;
133 		}
134 		return null;
135 	}
136 
createControllersFromDevices(RawInputEventQueue queue, List<Controller> controllers, List<RawDevice> devices, List<SetupAPIDevice> setupapi_devices)137 	private final static void createControllersFromDevices(RawInputEventQueue queue, List<Controller> controllers, List<RawDevice> devices, List<SetupAPIDevice> setupapi_devices) throws IOException {
138 		List<RawDevice> active_devices = new ArrayList<>();
139 		for (int i = 0; i < devices.size(); i++) {
140 			RawDevice device = devices.get(i);
141 			SetupAPIDevice setupapi_device = lookupSetupAPIDevice(device.getName(), setupapi_devices);
142 			if (setupapi_device == null) {
143 				/* Either the device is an RDP or we failed to locate the
144 				 * SetupAPI device that matches
145 				 */
146 				continue;
147 			}
148 			RawDeviceInfo info = device.getInfo();
149 			Controller controller = info.createControllerFromDevice(device, setupapi_device);
150 			if (controller != null) {
151 				controllers.add(controller);
152 				active_devices.add(device);
153 			}
154 		}
155 		queue.start(active_devices);
156 	}
157 
enumerateDevices(RawInputEventQueue queue, List<RawDevice> devices)158 	private final static native void enumerateDevices(RawInputEventQueue queue, List<RawDevice> devices) throws IOException;
159 
enumControllers(RawInputEventQueue queue)160 	private final Controller[] enumControllers(RawInputEventQueue queue) throws IOException {
161 		List<Controller> controllers = new ArrayList<>();
162 		List<RawDevice> devices = new ArrayList<>();
163 		enumerateDevices(queue, devices);
164 		List<SetupAPIDevice> setupapi_devices = enumSetupAPIDevices();
165 		createControllersFromDevices(queue, controllers, devices, setupapi_devices);
166 		Controller[] controllers_array = new Controller[controllers.size()];
167 		controllers.toArray(controllers_array);
168 		return controllers_array;
169 	}
170 
isSupported()171 	public boolean isSupported() {
172 		return supported;
173 	}
174 
175 	/*
176 	 * The raw input API, while being able to access
177 	 * multiple mice and keyboards, is a bit raw (hah)
178 	 * since it lacks some important features:
179 	 *
180 	 * 1. The list of keyboards and the list of mice
181 	 *    both include useless Terminal Server
182 	 *    devices (RDP_MOU and RDP_KEY) that we'd
183 	 *    like to skip.
184 	 * 2. The device names returned by GetRawInputDeviceInfo()
185 	 *    are not for display, but instead synthesized
186 	 *    from a combination of a device instance id
187 	 *    and a GUID.
188 	 *
189 	 * A solution to both problems is the SetupAPI that allows
190 	 * us to enumerate all keyboard and mouse devices and fetch their
191 	 * descriptive names and at the same time filter out the unwanted
192 	 * RDP devices.
193 	 */
enumSetupAPIDevices()194 	private final static List<SetupAPIDevice> enumSetupAPIDevices() throws IOException {
195 		List<SetupAPIDevice> devices = new ArrayList<>();
196 		nEnumSetupAPIDevices(getKeyboardClassGUID(), devices);
197 		nEnumSetupAPIDevices(getMouseClassGUID(), devices);
198 		return devices;
199 	}
nEnumSetupAPIDevices(byte[] guid, List<SetupAPIDevice> devices)200 	private final static native void nEnumSetupAPIDevices(byte[] guid, List<SetupAPIDevice> devices) throws IOException;
201 
getKeyboardClassGUID()202 	private final static native byte[] getKeyboardClassGUID();
getMouseClassGUID()203 	private final static native byte[] getMouseClassGUID();
204 
205 } // class DirectInputEnvironment
206