1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24#include <X11/Xlib.h>
25#include <X11/Xutil.h>
26
27#include "SDL_mouse.h"
28#include "../../events/SDL_events_c.h"
29#include "../SDL_cursor_c.h"
30#include "SDL_x11dga_c.h"
31#include "SDL_x11mouse_c.h"
32
33
34/* The implementation dependent data for the window manager cursor */
35struct WMcursor {
36	Cursor x_cursor;
37};
38
39
40void X11_FreeWMCursor(_THIS, WMcursor *cursor)
41{
42	if ( SDL_Display != NULL ) {
43		SDL_Lock_EventThread();
44		XFreeCursor(SDL_Display, cursor->x_cursor);
45		XSync(SDL_Display, False);
46		SDL_Unlock_EventThread();
47	}
48	SDL_free(cursor);
49}
50
51WMcursor *X11_CreateWMCursor(_THIS,
52		Uint8 *data, Uint8 *mask, int w, int h, int hot_x, int hot_y)
53{
54	WMcursor *cursor;
55	XGCValues GCvalues;
56	GC        GCcursor;
57	XImage *data_image, *mask_image;
58	Pixmap  data_pixmap, mask_pixmap;
59	int       clen, i;
60	char     *x_data, *x_mask;
61	static XColor black = {  0,  0,  0,  0 };
62	static XColor white = { 0xffff, 0xffff, 0xffff, 0xffff };
63
64	/* Allocate the cursor memory */
65	cursor = (WMcursor *)SDL_malloc(sizeof(WMcursor));
66	if ( cursor == NULL ) {
67		SDL_OutOfMemory();
68		return(NULL);
69	}
70
71	/* Mix the mask and the data */
72	clen = (w/8)*h;
73	x_data = (char *)SDL_malloc(clen);
74	if ( x_data == NULL ) {
75		SDL_free(cursor);
76		SDL_OutOfMemory();
77		return(NULL);
78	}
79	x_mask = (char *)SDL_malloc(clen);
80	if ( x_mask == NULL ) {
81		SDL_free(cursor);
82		SDL_free(x_data);
83		SDL_OutOfMemory();
84		return(NULL);
85	}
86	for ( i=0; i<clen; ++i ) {
87		/* The mask is OR'd with the data to turn inverted color
88		   pixels black since inverted color cursors aren't supported
89		   under X11.
90		 */
91		x_mask[i] = data[i] | mask[i];
92		x_data[i] = data[i];
93	}
94
95	/* Prevent the event thread from running while we use the X server */
96	SDL_Lock_EventThread();
97
98	/* Create the data image */
99	data_image = XCreateImage(SDL_Display,
100			DefaultVisual(SDL_Display, SDL_Screen),
101					1, XYBitmap, 0, x_data, w, h, 8, w/8);
102	data_image->byte_order = MSBFirst;
103	data_image->bitmap_bit_order = MSBFirst;
104	data_pixmap = XCreatePixmap(SDL_Display, SDL_Root, w, h, 1);
105
106	/* Create the data mask */
107	mask_image = XCreateImage(SDL_Display,
108			DefaultVisual(SDL_Display, SDL_Screen),
109					1, XYBitmap, 0, x_mask, w, h, 8, w/8);
110	mask_image->byte_order = MSBFirst;
111	mask_image->bitmap_bit_order = MSBFirst;
112	mask_pixmap = XCreatePixmap(SDL_Display, SDL_Root, w, h, 1);
113
114	/* Create the graphics context */
115	GCvalues.function = GXcopy;
116	GCvalues.foreground = ~0;
117	GCvalues.background =  0;
118	GCvalues.plane_mask = AllPlanes;
119	GCcursor = XCreateGC(SDL_Display, data_pixmap,
120			(GCFunction|GCForeground|GCBackground|GCPlaneMask),
121								&GCvalues);
122
123	/* Blit the images to the pixmaps */
124	XPutImage(SDL_Display, data_pixmap, GCcursor, data_image,
125							0, 0, 0, 0, w, h);
126	XPutImage(SDL_Display, mask_pixmap, GCcursor, mask_image,
127							0, 0, 0, 0, w, h);
128	XFreeGC(SDL_Display, GCcursor);
129	/* These free the x_data and x_mask memory pointers */
130	XDestroyImage(data_image);
131	XDestroyImage(mask_image);
132
133	/* Create the cursor */
134	cursor->x_cursor = XCreatePixmapCursor(SDL_Display, data_pixmap,
135				mask_pixmap, &black, &white, hot_x, hot_y);
136	XFreePixmap(SDL_Display, data_pixmap);
137	XFreePixmap(SDL_Display, mask_pixmap);
138
139	/* Release the event thread */
140	XSync(SDL_Display, False);
141	SDL_Unlock_EventThread();
142
143	return(cursor);
144}
145
146int X11_ShowWMCursor(_THIS, WMcursor *cursor)
147{
148	/* Don't do anything if the display is gone */
149	if ( SDL_Display == NULL ) {
150		return(0);
151	}
152
153	/* Set the X11 cursor cursor, or blank if cursor is NULL */
154	if ( SDL_Window ) {
155		SDL_Lock_EventThread();
156		if ( cursor == NULL ) {
157			if ( SDL_BlankCursor != NULL ) {
158				XDefineCursor(SDL_Display, SDL_Window,
159					SDL_BlankCursor->x_cursor);
160			}
161		} else {
162			XDefineCursor(SDL_Display, SDL_Window, cursor->x_cursor);
163		}
164		XSync(SDL_Display, False);
165		SDL_Unlock_EventThread();
166	}
167	return(1);
168}
169
170void X11_WarpWMCursor(_THIS, Uint16 x, Uint16 y)
171{
172	if ( using_dga & DGA_MOUSE ) {
173		SDL_PrivateMouseMotion(0, 0, x, y);
174	} else if ( mouse_relative) {
175		/*	RJR: March 28, 2000
176			leave physical cursor at center of screen if
177			mouse hidden and grabbed */
178		SDL_PrivateMouseMotion(0, 0, x, y);
179	} else {
180		SDL_Lock_EventThread();
181		XWarpPointer(SDL_Display, None, SDL_Window, 0, 0, 0, 0, x, y);
182		XSync(SDL_Display, False);
183		SDL_Unlock_EventThread();
184	}
185}
186
187/* Sets the mouse acceleration from a string of the form:
188	2/1/0
189   The first number is the numerator, followed by the acceleration
190   denumenator and threshold.
191*/
192static void SetMouseAccel(_THIS, const char *accel_param)
193{
194	int i;
195	size_t len;
196	int accel_value[3];
197	char *mouse_param, *mouse_param_buf, *pin;
198
199	len = SDL_strlen(accel_param)+1;
200	mouse_param_buf = SDL_stack_alloc(char, len);
201	if ( ! mouse_param_buf ) {
202		return;
203	}
204	SDL_strlcpy(mouse_param_buf, accel_param, len);
205	mouse_param = mouse_param_buf;
206
207	for ( i=0; (i < 3) && mouse_param; ++i ) {
208		pin = SDL_strchr(mouse_param, '/');
209		if ( pin ) {
210			*pin = '\0';
211		}
212		accel_value[i] = atoi(mouse_param);
213		if ( pin ) {
214			mouse_param = pin+1;
215		} else {
216			mouse_param = NULL;
217		}
218	}
219	if ( i == 3 ) {
220		XChangePointerControl(SDL_Display, True, True,
221			accel_value[0], accel_value[1], accel_value[2]);
222	}
223	SDL_stack_free(mouse_param_buf);
224}
225
226/* Check to see if we need to enter or leave mouse relative mode */
227void X11_CheckMouseModeNoLock(_THIS)
228{
229	const Uint8 full_focus = (SDL_APPACTIVE|SDL_APPINPUTFOCUS|SDL_APPMOUSEFOCUS);
230	char *env_override;
231	int enable_relative = 1;
232
233	/* This happens when quiting after an xio error */
234	if ( SDL_Display == NULL )
235	        return;
236
237	/* Allow the user to override the relative mouse mode.
238	   They almost never want to do this, as it seriously affects
239	   applications that rely on continuous relative mouse motion.
240	*/
241	env_override = SDL_getenv("SDL_MOUSE_RELATIVE");
242	if ( env_override ) {
243		enable_relative = atoi(env_override);
244	}
245
246	/* If the mouse is hidden and input is grabbed, we use relative mode */
247	if ( enable_relative &&
248	     !(SDL_cursorstate & CURSOR_VISIBLE) &&
249	     (this->input_grab != SDL_GRAB_OFF) &&
250             (SDL_GetAppState() & full_focus) == full_focus ) {
251		if ( ! mouse_relative ) {
252			X11_EnableDGAMouse(this);
253			if ( ! (using_dga & DGA_MOUSE) ) {
254				char *xmouse_accel;
255
256				SDL_GetMouseState(&mouse_last.x, &mouse_last.y);
257				/* Use as raw mouse mickeys as possible */
258				XGetPointerControl(SDL_Display,
259						&mouse_accel.numerator,
260						&mouse_accel.denominator,
261						&mouse_accel.threshold);
262				xmouse_accel=SDL_getenv("SDL_VIDEO_X11_MOUSEACCEL");
263				if ( xmouse_accel ) {
264					SetMouseAccel(this, xmouse_accel);
265				}
266			}
267			mouse_relative = 1;
268		}
269	} else {
270		if ( mouse_relative ) {
271			if ( using_dga & DGA_MOUSE ) {
272				X11_DisableDGAMouse(this);
273			} else {
274				XChangePointerControl(SDL_Display, True, True,
275						mouse_accel.numerator,
276						mouse_accel.denominator,
277						mouse_accel.threshold);
278			}
279			mouse_relative = 0;
280		}
281	}
282}
283void X11_CheckMouseMode(_THIS)
284{
285	SDL_Lock_EventThread();
286	X11_CheckMouseModeNoLock(this);
287	SDL_Unlock_EventThread();
288}
289