1 /*
2  * Copyright (c) 2002-2008 LWJGL Project
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'LWJGL' nor the names of
17  *   its contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package org.lwjgl.opengl;
33 
34 /**
35  * @author elias_naur
36  */
37 
38 import java.nio.ByteBuffer;
39 import java.nio.IntBuffer;
40 
41 import org.lwjgl.BufferUtils;
42 import org.lwjgl.LWJGLException;
43 import org.lwjgl.input.Mouse;
44 
45 final class LinuxMouse {
46 	private static final int POINTER_WARP_BORDER = 10;
47 	// scale the mouse wheel according to DirectInput
48 	private static final int WHEEL_SCALE = 120;
49 
50 	private int button_count;
51 
52 	/* X11 constants */
53 	private static final int Button1 = 1;
54 	private static final int Button2 = 2;
55 	private static final int Button3 = 3;
56 	private static final int Button4 = 4;
57 	private static final int Button5 = 5;
58 
59 	private static final int Button6 = 6; // wheel tilt left *rare*
60 	private static final int Button7 = 7; // wheel tilt right *rare*
61 	private static final int Button8 = 8; // back button
62 	private static final int Button9 = 9; // forward button
63 
64 	private static final int ButtonPress = 4;
65 	private static final int ButtonRelease = 5;
66 
67 	private final long display;
68 	private final long window;
69 	private final long input_window;
70 	private final long warp_atom;
71 	private final IntBuffer query_pointer_buffer = BufferUtils.createIntBuffer(4);
72 	private final ByteBuffer event_buffer = ByteBuffer.allocate(Mouse.EVENT_SIZE);
73 
74 	private int last_x;
75 	private int last_y;
76 	private int accum_dx;
77 	private int accum_dy;
78 	private int accum_dz;
79 	private byte[] buttons;
80 	private EventQueue event_queue;
81 	private long last_event_nanos;
82 
LinuxMouse(long display, long window, long input_window)83 	LinuxMouse(long display, long window, long input_window) throws LWJGLException {
84 		this.display = display;
85 		this.window = window;
86 		this.input_window = input_window;
87 		this.warp_atom = LinuxDisplay.nInternAtom(display, "_LWJGL", false);
88 		button_count = nGetButtonCount(display);
89 		buttons = new byte[button_count];
90 		reset(false, false);
91 	}
92 
reset(boolean grab, boolean warp_pointer)93 	private void reset(boolean grab, boolean warp_pointer) {
94 		event_queue = new EventQueue(event_buffer.capacity());
95 		accum_dx = accum_dy = 0;
96 		long root_window = nQueryPointer(display, window, query_pointer_buffer);
97 
98 		int root_x = query_pointer_buffer.get(0);
99 		int root_y = query_pointer_buffer.get(1);
100 		int win_x = query_pointer_buffer.get(2);
101 		int win_y = query_pointer_buffer.get(3);
102 		// Pretend that the cursor never moved
103 		last_x = win_x;
104 		last_y = transformY(win_y);
105 		doHandlePointerMotion(grab, warp_pointer, root_window, root_x, root_y, win_x, win_y, last_event_nanos);
106 	}
107 
read(ByteBuffer buffer)108 	public void read(ByteBuffer buffer) {
109 		event_queue.copyEvents(buffer);
110 	}
111 
poll(boolean grab, IntBuffer coord_buffer, ByteBuffer buttons_buffer)112 	public void poll(boolean grab, IntBuffer coord_buffer, ByteBuffer buttons_buffer) {
113 		if (grab) {
114 			coord_buffer.put(0, accum_dx);
115 			coord_buffer.put(1, accum_dy);
116 		} else {
117 			coord_buffer.put(0, last_x);
118 			coord_buffer.put(1, last_y);
119 		}
120 		coord_buffer.put(2, accum_dz);
121 		accum_dx = accum_dy = accum_dz = 0;
122 		for (int i = 0; i < buttons.length; i++)
123 			buttons_buffer.put(i, buttons[i]);
124 	}
125 
putMouseEventWithCoords(byte button, byte state, int coord1, int coord2, int dz, long nanos)126 	private void putMouseEventWithCoords(byte button, byte state, int coord1, int coord2, int dz, long nanos) {
127 		event_buffer.clear();
128 		event_buffer.put(button).put(state).putInt(coord1).putInt(coord2).putInt(dz).putLong(nanos);
129 		event_buffer.flip();
130 		event_queue.putEvent(event_buffer);
131 		last_event_nanos = nanos;
132 	}
133 
setCursorPos(boolean grab, int x, int y, long nanos)134 	private void setCursorPos(boolean grab, int x, int y, long nanos) {
135 		y = transformY(y);
136 		int dx = x - last_x;
137 		int dy = y - last_y;
138 		if (dx != 0 || dy != 0) {
139 			accum_dx += dx;
140 			accum_dy += dy;
141 			last_x = x;
142 			last_y = y;
143 			if (grab) {
144 				putMouseEventWithCoords((byte)-1, (byte)0, dx, dy, 0, nanos);
145 			} else {
146 				putMouseEventWithCoords((byte)-1, (byte)0, x, y, 0, nanos);
147 			}
148 		}
149 	}
150 
doWarpPointer(int center_x, int center_y)151 	private void doWarpPointer(int center_x, int center_y) {
152 		nSendWarpEvent(display, input_window, warp_atom, center_x, center_y);
153 		nWarpCursor(display, window, center_x, center_y);
154 	}
nSendWarpEvent(long display, long window, long warp_atom, int center_x, int center_y)155 	private static native void nSendWarpEvent(long display, long window, long warp_atom, int center_x, int center_y);
156 
doHandlePointerMotion(boolean grab, boolean warp_pointer, long root_window, int root_x, int root_y, int win_x, int win_y, long nanos)157 	private void doHandlePointerMotion(boolean grab, boolean warp_pointer, long root_window, int root_x, int root_y, int win_x, int win_y, long nanos) {
158 		setCursorPos(grab, win_x, win_y, nanos);
159 		if (!warp_pointer)
160 			return;
161 		int root_window_height = nGetWindowHeight(display, root_window);
162 		int root_window_width = nGetWindowWidth(display, root_window);
163 		int window_height = nGetWindowHeight(display, window);
164 		int window_width = nGetWindowWidth(display, window);
165 
166 		// find the window position in root coordinates
167 		int win_left = root_x - win_x;
168 		int win_top = root_y - win_y;
169 		int win_right = win_left + window_width;
170 		int win_bottom = win_top + window_height;
171 		// cap the window position to the screen dimensions
172 		int border_left = Math.max(0, win_left);
173 		int border_top = Math.max(0, win_top);
174 		int border_right = Math.min(root_window_width, win_right);
175 		int border_bottom = Math.min(root_window_height, win_bottom);
176 		// determine whether the cursor is outside the bounds
177 		boolean outside_limits = root_x < border_left + POINTER_WARP_BORDER || root_y < border_top + POINTER_WARP_BORDER ||
178 			root_x > border_right - POINTER_WARP_BORDER || root_y > border_bottom - POINTER_WARP_BORDER;
179 		if (outside_limits) {
180 			// Find the center of the limits in window coordinates
181 			int center_x = (border_right - border_left)/2;
182 			int center_y = (border_bottom - border_top)/2;
183 			doWarpPointer(center_x, center_y);
184 		}
185 	}
186 
changeGrabbed(boolean grab, boolean warp_pointer)187 	public void changeGrabbed(boolean grab, boolean warp_pointer) {
188 		reset(grab, warp_pointer);
189 	}
190 
getButtonCount()191 	public int getButtonCount() {
192 		return buttons.length;
193 	}
194 
transformY(int y)195 	private int transformY(int y) {
196 		return nGetWindowHeight(display, window) - 1 - y;
197 	}
nGetWindowHeight(long display, long window)198 	private static native int nGetWindowHeight(long display, long window);
nGetWindowWidth(long display, long window)199 	private static native int nGetWindowWidth(long display, long window);
200 
nGetButtonCount(long display)201 	private static native int nGetButtonCount(long display);
202 
nQueryPointer(long display, long window, IntBuffer result)203 	private static native long nQueryPointer(long display, long window, IntBuffer result);
204 
setCursorPosition(int x, int y)205 	public void setCursorPosition(int x, int y) {
206 		nWarpCursor(display, window, x, transformY(y));
207 	}
nWarpCursor(long display, long window, int x, int y)208 	private static native void nWarpCursor(long display, long window, int x, int y);
209 
handlePointerMotion(boolean grab, boolean warp_pointer, long millis, long root_window, int x_root, int y_root, int x, int y)210 	private void handlePointerMotion(boolean grab, boolean warp_pointer, long millis, long root_window, int x_root, int y_root, int x, int y) {
211 		doHandlePointerMotion(grab, warp_pointer, root_window, x_root, y_root, x, y, millis*1000000);
212 	}
213 
handleButton(boolean grab, int button, byte state, long nanos)214 	private void handleButton(boolean grab, int button, byte state, long nanos) {
215 		byte button_num;
216 		switch (button) {
217 			case Button1:
218 				button_num = (byte)0;
219 				break;
220 			case Button2:
221 				button_num = (byte)2;
222 				break;
223 			case Button3:
224 				button_num = (byte)1;
225 				break;
226 			case Button6:
227 				button_num = (byte)5;
228 				break;
229 			case Button7:
230 				button_num = (byte)6;
231 				break;
232 			case Button8:
233 				button_num = (byte)3; // back button
234 				break;
235 			case Button9:
236 				button_num = (byte)4; // forward button
237 				break;
238 			default:
239 				if (button > Button9 && button <= button_count) {
240 					button_num = (byte)(button-1);
241 					break;
242 				}
243 				return;
244 		}
245 		buttons[button_num] = state;
246 		putMouseEvent(grab, button_num, state, 0, nanos);
247 	}
248 
putMouseEvent(boolean grab, byte button, byte state, int dz, long nanos)249 	private void putMouseEvent(boolean grab, byte button, byte state, int dz, long nanos) {
250 		if (grab)
251 			putMouseEventWithCoords(button, state, 0, 0, dz, nanos);
252 		else
253 			putMouseEventWithCoords(button, state, last_x, last_y, dz, nanos);
254 	}
255 
handleButtonPress(boolean grab, byte button, long nanos)256 	private void handleButtonPress(boolean grab, byte button, long nanos) {
257 		int delta = 0;
258 		switch (button) {
259 			case Button4:
260 				delta = WHEEL_SCALE;
261 				putMouseEvent(grab, (byte)-1, (byte)0, delta, nanos);
262 				accum_dz += delta;
263 				break;
264 			case Button5:
265 				delta = -WHEEL_SCALE;
266 				putMouseEvent(grab, (byte)-1, (byte)0, delta, nanos);
267 				accum_dz += delta;
268 				break;
269 			default:
270 				handleButton(grab, button, (byte)1, nanos);
271 				break;
272 		}
273 	}
274 
handleButtonEvent(boolean grab, long millis, int type, byte button)275 	private void handleButtonEvent(boolean grab, long millis, int type, byte button) {
276 		long nanos = millis*1000000;
277 		switch (type) {
278 			case ButtonRelease:
279 				handleButton(grab, button, (byte)0, nanos);
280 				break;
281 			case ButtonPress:
282 				handleButtonPress(grab, button, nanos);
283 				break;
284 			default:
285 				break;
286 		}
287 	}
288 
resetCursor(int x, int y)289 	private void resetCursor(int x, int y) {
290 		last_x = x;
291 		last_y = transformY(y);
292 	}
293 
handleWarpEvent(int x, int y)294 	private void handleWarpEvent(int x, int y) {
295 		resetCursor(x, y);
296 	}
297 
filterEvent(boolean grab, boolean warp_pointer, LinuxEvent event)298 	public boolean filterEvent(boolean grab, boolean warp_pointer, LinuxEvent event) {
299 		switch (event.getType()) {
300 			case LinuxEvent.ClientMessage:
301 				if (event.getClientMessageType() == warp_atom) {
302 					handleWarpEvent(event.getClientData(0), event.getClientData(1));
303 					return true;
304 				}
305 				break;
306 			case LinuxEvent.ButtonPress: /* Fall through */
307 			case LinuxEvent.ButtonRelease:
308 				handleButtonEvent(grab, event.getButtonTime(), event.getButtonType(), (byte)event.getButtonButton());
309 				return true;
310 			case LinuxEvent.MotionNotify:
311 				handlePointerMotion(grab, warp_pointer, event.getButtonTime(), event.getButtonRoot(), event.getButtonXRoot(), event.getButtonYRoot(), event.getButtonX(), event.getButtonY());
312 				return true;
313 			default:
314 				break;
315 		}
316 		return false;
317 	}
318 }
319