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 <stdio.h>
25#include <unistd.h>
26
27#include "SDL_endian.h"
28#include "../../events/SDL_events_c.h"
29#include "SDL_x11image_c.h"
30
31#ifndef NO_SHARED_MEMORY
32
33/* Shared memory error handler routine */
34static int shm_error;
35static int (*X_handler)(Display *, XErrorEvent *) = NULL;
36static int shm_errhandler(Display *d, XErrorEvent *e)
37{
38        if ( e->error_code == BadAccess ) {
39        	shm_error = True;
40        	return(0);
41        } else
42		return(X_handler(d,e));
43}
44
45static void try_mitshm(_THIS, SDL_Surface *screen)
46{
47	/* Dynamic X11 may not have SHM entry points on this box. */
48	if ((use_mitshm) && (!SDL_X11_HAVE_SHM))
49		use_mitshm = 0;
50
51	if(!use_mitshm)
52		return;
53	shminfo.shmid = shmget(IPC_PRIVATE, screen->h*screen->pitch,
54			       IPC_CREAT | 0777);
55	if ( shminfo.shmid >= 0 ) {
56		shminfo.shmaddr = (char *)shmat(shminfo.shmid, 0, 0);
57		shminfo.readOnly = False;
58		if ( shminfo.shmaddr != (char *)-1 ) {
59			shm_error = False;
60			X_handler = XSetErrorHandler(shm_errhandler);
61			XShmAttach(SDL_Display, &shminfo);
62			XSync(SDL_Display, True);
63			XSetErrorHandler(X_handler);
64			if ( shm_error )
65				shmdt(shminfo.shmaddr);
66		} else {
67			shm_error = True;
68		}
69		shmctl(shminfo.shmid, IPC_RMID, NULL);
70	} else {
71		shm_error = True;
72	}
73	if ( shm_error )
74		use_mitshm = 0;
75	if ( use_mitshm )
76		screen->pixels = shminfo.shmaddr;
77}
78#endif /* ! NO_SHARED_MEMORY */
79
80/* Various screen update functions available */
81static void X11_NormalUpdate(_THIS, int numrects, SDL_Rect *rects);
82static void X11_MITSHMUpdate(_THIS, int numrects, SDL_Rect *rects);
83
84int X11_SetupImage(_THIS, SDL_Surface *screen)
85{
86#ifndef NO_SHARED_MEMORY
87	try_mitshm(this, screen);
88	if(use_mitshm) {
89		SDL_Ximage = XShmCreateImage(SDL_Display, SDL_Visual,
90					     this->hidden->depth, ZPixmap,
91					     shminfo.shmaddr, &shminfo,
92					     screen->w, screen->h);
93		if(!SDL_Ximage) {
94			XShmDetach(SDL_Display, &shminfo);
95			XSync(SDL_Display, False);
96			shmdt(shminfo.shmaddr);
97			screen->pixels = NULL;
98			goto error;
99		}
100		this->UpdateRects = X11_MITSHMUpdate;
101	}
102	if(!use_mitshm)
103#endif /* not NO_SHARED_MEMORY */
104	{
105		screen->pixels = SDL_malloc(screen->h*screen->pitch);
106		if ( screen->pixels == NULL ) {
107			SDL_OutOfMemory();
108			return -1;
109		}
110		SDL_Ximage = XCreateImage(SDL_Display, SDL_Visual,
111					  this->hidden->depth, ZPixmap, 0,
112					  (char *)screen->pixels,
113					  screen->w, screen->h,
114					  32, 0);
115		if ( SDL_Ximage == NULL )
116			goto error;
117		/* XPutImage will convert byte sex automatically */
118		SDL_Ximage->byte_order = (SDL_BYTEORDER == SDL_BIG_ENDIAN)
119			                 ? MSBFirst : LSBFirst;
120		this->UpdateRects = X11_NormalUpdate;
121	}
122	screen->pitch = SDL_Ximage->bytes_per_line;
123	return(0);
124
125error:
126	SDL_SetError("Couldn't create XImage");
127	return 1;
128}
129
130void X11_DestroyImage(_THIS, SDL_Surface *screen)
131{
132	if ( SDL_Ximage ) {
133		XDestroyImage(SDL_Ximage);
134#ifndef NO_SHARED_MEMORY
135		if ( use_mitshm ) {
136			XShmDetach(SDL_Display, &shminfo);
137			XSync(SDL_Display, False);
138			shmdt(shminfo.shmaddr);
139		}
140#endif /* ! NO_SHARED_MEMORY */
141		SDL_Ximage = NULL;
142	}
143	if ( screen ) {
144		screen->pixels = NULL;
145	}
146}
147
148/* Determine the number of CPUs in the system */
149static int num_CPU(void)
150{
151       static int num_cpus = 0;
152
153       if(!num_cpus) {
154#if defined(__LINUX__)
155           char line[BUFSIZ];
156           FILE *pstat = fopen("/proc/stat", "r");
157           if ( pstat ) {
158               while ( fgets(line, sizeof(line), pstat) ) {
159                   if (SDL_memcmp(line, "cpu", 3) == 0 && line[3] != ' ') {
160                       ++num_cpus;
161                   }
162               }
163               fclose(pstat);
164           }
165#elif defined(__IRIX__)
166	   num_cpus = sysconf(_SC_NPROC_ONLN);
167#elif defined(_SC_NPROCESSORS_ONLN)
168	   /* number of processors online (SVR4.0MP compliant machines) */
169           num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
170#elif defined(_SC_NPROCESSORS_CONF)
171	   /* number of processors configured (SVR4.0MP compliant machines) */
172           num_cpus = sysconf(_SC_NPROCESSORS_CONF);
173#endif
174           if ( num_cpus <= 0 ) {
175               num_cpus = 1;
176           }
177       }
178       return num_cpus;
179}
180
181int X11_ResizeImage(_THIS, SDL_Surface *screen, Uint32 flags)
182{
183	int retval;
184
185	X11_DestroyImage(this, screen);
186        if ( flags & SDL_OPENGL ) {  /* No image when using GL */
187        	retval = 0;
188        } else {
189		retval = X11_SetupImage(this, screen);
190		/* We support asynchronous blitting on the display */
191		if ( flags & SDL_ASYNCBLIT ) {
192			/* This is actually slower on single-CPU systems,
193			   probably because of CPU contention between the
194			   X server and the application.
195			   Note: Is this still true with XFree86 4.0?
196			*/
197			if ( num_CPU() > 1 ) {
198				screen->flags |= SDL_ASYNCBLIT;
199			}
200		}
201	}
202	return(retval);
203}
204
205/* We don't actually allow hardware surfaces other than the main one */
206int X11_AllocHWSurface(_THIS, SDL_Surface *surface)
207{
208	return(-1);
209}
210void X11_FreeHWSurface(_THIS, SDL_Surface *surface)
211{
212	return;
213}
214
215int X11_LockHWSurface(_THIS, SDL_Surface *surface)
216{
217	if ( (surface == SDL_VideoSurface) && blit_queued ) {
218		XSync(GFX_Display, False);
219		blit_queued = 0;
220	}
221	return(0);
222}
223void X11_UnlockHWSurface(_THIS, SDL_Surface *surface)
224{
225	return;
226}
227
228int X11_FlipHWSurface(_THIS, SDL_Surface *surface)
229{
230	return(0);
231}
232
233static void X11_NormalUpdate(_THIS, int numrects, SDL_Rect *rects)
234{
235	int i;
236
237	for (i = 0; i < numrects; ++i) {
238		if ( rects[i].w == 0 || rects[i].h == 0 ) { /* Clipped? */
239			continue;
240		}
241		XPutImage(GFX_Display, SDL_Window, SDL_GC, SDL_Ximage,
242			  rects[i].x, rects[i].y,
243			  rects[i].x, rects[i].y, rects[i].w, rects[i].h);
244	}
245	if ( SDL_VideoSurface->flags & SDL_ASYNCBLIT ) {
246		XFlush(GFX_Display);
247		blit_queued = 1;
248	} else {
249		XSync(GFX_Display, False);
250	}
251}
252
253static void X11_MITSHMUpdate(_THIS, int numrects, SDL_Rect *rects)
254{
255#ifndef NO_SHARED_MEMORY
256	int i;
257
258	for ( i=0; i<numrects; ++i ) {
259		if ( rects[i].w == 0 || rects[i].h == 0 ) { /* Clipped? */
260			continue;
261		}
262		XShmPutImage(GFX_Display, SDL_Window, SDL_GC, SDL_Ximage,
263				rects[i].x, rects[i].y,
264				rects[i].x, rects[i].y, rects[i].w, rects[i].h,
265									False);
266	}
267	if ( SDL_VideoSurface->flags & SDL_ASYNCBLIT ) {
268		XFlush(GFX_Display);
269		blit_queued = 1;
270	} else {
271		XSync(GFX_Display, False);
272	}
273#endif /* ! NO_SHARED_MEMORY */
274}
275
276/* There's a problem with the automatic refreshing of the display.
277   Even though the XVideo code uses the GFX_Display to update the
278   video memory, it appears that updating the window asynchronously
279   from a different thread will cause "blackouts" of the window.
280   This is a sort of a hacked workaround for the problem.
281*/
282static int enable_autorefresh = 1;
283
284void X11_DisableAutoRefresh(_THIS)
285{
286	--enable_autorefresh;
287}
288
289void X11_EnableAutoRefresh(_THIS)
290{
291	++enable_autorefresh;
292}
293
294void X11_RefreshDisplay(_THIS)
295{
296	/* Don't refresh a display that doesn't have an image (like GL)
297	   Instead, post an expose event so the application can refresh.
298	 */
299	if ( ! SDL_Ximage || (enable_autorefresh <= 0) ) {
300		SDL_PrivateExpose();
301		return;
302	}
303#ifndef NO_SHARED_MEMORY
304	if ( this->UpdateRects == X11_MITSHMUpdate ) {
305		XShmPutImage(SDL_Display, SDL_Window, SDL_GC, SDL_Ximage,
306				0, 0, 0, 0, this->screen->w, this->screen->h,
307				False);
308	} else
309#endif /* ! NO_SHARED_MEMORY */
310	{
311		XPutImage(SDL_Display, SDL_Window, SDL_GC, SDL_Ximage,
312			  0, 0, 0, 0, this->screen->w, this->screen->h);
313	}
314	XSync(SDL_Display, False);
315}
316
317