LGPL
[vlc.git] / modules / access / screen / mac.c
1 /*****************************************************************************
2  * mac.c: Screen capture module for the Mac.
3  *****************************************************************************
4  * Copyright (C) 2004, 2008 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Derk-Jan Hartman <hartman at videolan dot org>
8  *          arai <arai_a@mac.com>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # import "config.h"
31 #endif
32
33 #import <vlc_common.h>
34
35 // Fix ourselves ColorSync headers that gets included in ApplicationServices.
36 #define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
37 #define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
38 #define __MACHINEEXCEPTIONS__
39 #import <ApplicationServices/ApplicationServices.h>
40
41 #import <OpenGL/OpenGL.h>
42 #import <OpenGL/gl.h>
43 #import <stdlib.h>
44
45 typedef int CGSConnectionRef;
46 extern CGError CGSNewConnection( void *, CGSConnectionRef * );
47 extern CGError CGSReleaseConnection( CGSConnectionRef );
48 extern CGError CGSGetGlobalCursorDataSize( CGSConnectionRef, int * );
49 extern CGError CGSGetGlobalCursorData( CGSConnectionRef, unsigned char *,
50                                        int *, int *, CGRect *, CGPoint *,
51                                        int *, int *, int * );
52 extern CGError CGSGetCurrentCursorLocation( CGSConnectionRef, CGPoint * );
53 extern int CGSCurrentCursorSeed( void );
54
55 typedef CGImageRef ( *typeofCGDisplayCreateImageForRect )( CGDirectDisplayID displayID, CGRect rect );
56
57 #import "screen.h"
58
59 struct screen_data_t
60 {
61     CGLContextObj screen;
62     char *screen_image;
63
64     CGLContextObj clipped;
65     char *clipped_image;
66
67     GLuint cursor_texture;
68     int cursor_seed;
69     bool cursor_need_update;
70     CGRect cursor_rect;
71     CGPoint cursor_hot;
72     double cursor_texture_map_u, cursor_texture_map_v;
73
74     int width;
75     int height;
76
77     int screen_top;
78     int screen_left;
79     int screen_width;
80     int screen_height;
81
82     CGSConnectionRef connection;
83
84     CFBundleRef bundle;
85
86     typeofCGDisplayCreateImageForRect myCGDisplayCreateImageForRect;
87
88     CGDirectDisplayID display_id;
89 };
90
91 CGLError screen_CreateContext( CGLContextObj *context,
92                                CGLPixelFormatAttribute a0,
93                                CGLPixelFormatAttribute a1,
94                                CGLPixelFormatAttribute a2,
95                                CGLPixelFormatAttribute a3 );
96 int screen_DrawCursor( demux_sys_t *p_sys, CGPoint *cursor_pos );
97 int screen_CaptureScreen( demux_sys_t *p_sys );
98
99 int screen_InitCapture( demux_t *p_demux )
100 {
101     demux_sys_t   *p_sys = p_demux->p_sys;
102     screen_data_t *p_data;
103     CGLError returnedError;
104     unsigned int i;
105
106     p_sys->p_data = p_data =
107         ( screen_data_t * )calloc( 1, sizeof( screen_data_t ) );
108
109     p_data->display_id = kCGDirectMainDisplay;
110
111     unsigned int displayCount;
112     displayCount = 0;
113     returnedError = CGGetOnlineDisplayList( 0, NULL, &displayCount );
114     if( !returnedError )
115     {
116         CGDirectDisplayID *ids;
117         ids = ( CGDirectDisplayID * )malloc( displayCount * sizeof( CGDirectDisplayID ) );
118         returnedError = CGGetOnlineDisplayList( displayCount, ids, &displayCount );
119         if( !returnedError )
120         {
121             if ( p_sys->i_display_id > 0 )
122             {
123                 for( i = 0; i < displayCount; i ++ )
124                 {
125                     if( p_sys->i_display_id == ids[i] )
126                     {
127                         p_data->display_id = ids[i];
128                         break;
129                     }
130                 }
131             }
132             else if ( p_sys->i_screen_index > 0 && p_sys->i_screen_index <= displayCount )
133             {
134                 p_data->display_id = ids[p_sys->i_screen_index - 1];
135             }
136         }
137         free( ids );
138     }
139
140     /* CGImage Function
141      *   CGDisplayCreateImageForRect is available in Mac OS X v10.6 and later */
142
143     p_data->myCGDisplayCreateImageForRect = NULL;
144
145     CFURLRef frameworkURL = NULL;
146     CFStringRef path = CFSTR( "file://localhost/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework" );
147     frameworkURL = CFURLCreateWithString( kCFAllocatorDefault, path, NULL );
148     if( frameworkURL != NULL )
149     {
150         p_data->bundle = CFBundleCreate( kCFAllocatorDefault, frameworkURL );
151         if( p_data->bundle != NULL )
152         {
153             p_data->myCGDisplayCreateImageForRect =
154                 ( typeofCGDisplayCreateImageForRect )CFBundleGetFunctionPointerForName
155                 ( p_data->bundle, CFSTR( "CGDisplayCreateImageForRect" ) );
156         }
157
158         CFRelease( frameworkURL );
159     }
160
161     /* Screen Size */
162
163     CGRect rect = CGDisplayBounds( p_data->display_id );
164     p_data->screen_left = rect.origin.x;
165     p_data->screen_top = rect.origin.y;
166     p_data->screen_width = rect.size.width;
167     p_data->screen_height = rect.size.height;
168
169     p_data->width = p_sys->i_width;
170     p_data->height = p_sys->i_height;
171     if( p_data->width <= 0 || p_data->height <= 0 )
172     {
173         p_data->width = p_data->screen_width;
174         p_data->height = p_data->screen_height;
175     }
176
177     /* Screen Context */
178
179     if( p_data->myCGDisplayCreateImageForRect == NULL )
180     {
181         returnedError =
182             screen_CreateContext( &p_data->screen,
183                                   kCGLPFAFullScreen,
184                                   kCGLPFADisplayMask,
185                                   ( CGLPixelFormatAttribute )CGDisplayIDToOpenGLDisplayMask( p_data->display_id ),
186                                   ( CGLPixelFormatAttribute )0 );
187         if( returnedError )
188             goto errorHandling;
189
190         returnedError = CGLSetCurrentContext( p_data->screen );
191         if( returnedError )
192             goto errorHandling;
193
194         returnedError = CGLSetFullScreen( p_data->screen );
195         if( returnedError )
196             goto errorHandling;
197     }
198
199     /* Clipped Context */
200
201     returnedError =
202         screen_CreateContext( &p_data->clipped,
203                               kCGLPFAOffScreen,
204                               kCGLPFAColorSize,
205                               ( CGLPixelFormatAttribute )32,
206                               ( CGLPixelFormatAttribute )0 );
207     if( returnedError )
208         goto errorHandling;
209
210     returnedError = CGLSetCurrentContext( p_data->clipped );
211     if( returnedError )
212         goto errorHandling;
213
214     /* Clipped Image */
215
216     p_data->clipped_image =
217         ( char * )malloc( p_data->width * p_data->height * 4 );
218
219     returnedError = CGLSetOffScreen( p_data->clipped, p_data->width, p_data->height, p_data->width * 4, p_data->clipped_image );
220     if( returnedError )
221         goto errorHandling;
222
223     /* Screen Image */
224
225     if( p_data->myCGDisplayCreateImageForRect != NULL )
226     {
227         p_data->screen_image =
228             ( char * )malloc( p_data->screen_width * p_data->screen_height * 4 );
229     }
230     else
231     {
232         p_data->screen_image =
233             ( char * )malloc( p_data->width * p_data->height * 4 );
234     }
235
236     /* Cursor */
237
238     CGSNewConnection( NULL, &( p_data->connection ) );
239
240     p_data->cursor_need_update = 1;
241     p_data->cursor_seed = 0;
242
243     glGenTextures( 1, &( p_data->cursor_texture ) );
244     glBindTexture( GL_TEXTURE_2D, p_data->cursor_texture );
245
246     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
247     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
248     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
249     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
250
251     /* System */
252
253     es_format_Init( &p_sys->fmt, VIDEO_ES, VLC_CODEC_RGB32 );
254
255     /* p_sys->fmt.video.i_* must set to screen size, not subscreen size */
256     p_sys->fmt.video.i_width = p_data->screen_width;
257     p_sys->fmt.video.i_visible_width = p_data->screen_width;
258     p_sys->fmt.video.i_height = p_data->screen_height;
259     p_sys->fmt.video.i_bits_per_pixel = 32;
260
261     return VLC_SUCCESS;
262
263     errorHandling:
264     msg_Err( p_demux, "Core OpenGL failure: %s", CGLErrorString( returnedError ) );
265     return VLC_EGENERIC;
266 }
267
268 int screen_CloseCapture( demux_t *p_demux )
269 {
270     screen_data_t *p_data = ( screen_data_t * )p_demux->p_sys->p_data;
271
272     CGLSetCurrentContext( NULL );
273
274     /* Cursor */
275
276     glBindTexture( GL_TEXTURE_2D, 0 );
277
278     glDeleteTextures( 1, &( p_data->cursor_texture ) );
279
280     CGSReleaseConnection( p_data->connection );
281
282     /* Screen Image */
283     if( p_data->screen_image != NULL )
284     {
285         free( p_data->screen_image );
286         p_data->screen_image = NULL;
287     }
288
289     /* Clipped Image */
290
291     if( p_data->clipped_image != NULL )
292     {
293         free( p_data->clipped_image );
294         p_data->clipped_image = NULL;
295     }
296
297     /* Clipped Context */
298
299     CGLClearDrawable( p_data->clipped );
300     CGLDestroyContext( p_data->clipped );
301
302     /* Screen Context */
303
304     if( p_data->myCGDisplayCreateImageForRect == NULL )
305     {
306         CGLClearDrawable( p_data->screen );
307         CGLDestroyContext( p_data->screen );
308     }
309
310     /* CGImage */
311
312     CFRelease( p_data->bundle );
313
314     free( p_data );
315
316     return VLC_SUCCESS;
317 }
318
319 block_t *screen_Capture( demux_t *p_demux )
320 {
321     demux_sys_t *p_sys = p_demux->p_sys;
322     screen_data_t *p_data = ( screen_data_t * )p_sys->p_data;
323     block_t *p_block;
324     int i_size;
325
326     i_size = p_sys->fmt.video.i_height * p_sys->fmt.video.i_width * 4;
327
328     if( !( p_block = block_New( p_demux, i_size ) ) )
329     {
330         msg_Warn( p_demux, "cannot get block" );
331         return NULL;
332     }
333
334     CGPoint cursor_pos;
335     CGError cursor_result;
336
337     cursor_pos.x = 0;
338     cursor_pos.y = 0;
339
340     cursor_result =
341         CGSGetCurrentCursorLocation( p_data->connection, &cursor_pos );
342
343     cursor_pos.x -= p_data->screen_left;
344     cursor_pos.y -= p_data->screen_top;
345
346     if( p_sys->b_follow_mouse
347         && cursor_result == kCGErrorSuccess )
348     {
349         FollowMouse( p_sys, cursor_pos.x, cursor_pos.y );
350     }
351
352     screen_CaptureScreen( p_sys );
353
354     CGLSetCurrentContext( p_data->clipped );
355
356     glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
357     glClear( GL_COLOR_BUFFER_BIT );
358
359     glDrawPixels( p_data->width,
360                   p_data->height,
361                   GL_RGBA, GL_UNSIGNED_BYTE, p_data->screen_image );
362
363     if( cursor_result == kCGErrorSuccess )
364     {
365         screen_DrawCursor( p_sys, &cursor_pos );
366     }
367
368     glReadPixels( 0, 0,
369                   p_data->width,
370                   p_data->height,
371                   GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
372                   p_block->p_buffer );
373
374     return p_block;
375 }
376
377 CGLError screen_CreateContext( CGLContextObj *context,
378                                CGLPixelFormatAttribute a0,
379                                CGLPixelFormatAttribute a1,
380                                CGLPixelFormatAttribute a2,
381                                CGLPixelFormatAttribute a3 )
382 {
383     CGLPixelFormatAttribute attribs[4];
384     CGLPixelFormatObj pix;
385     GLint npix;
386     CGLError returnedError;
387
388     attribs[0] = a0;
389     attribs[1] = a1;
390     attribs[2] = a2;
391     attribs[3] = a3;
392
393     returnedError = CGLChoosePixelFormat( attribs, &pix, &npix );
394     if( returnedError )
395     {
396         return returnedError;
397     }
398
399     returnedError = CGLCreateContext( pix, NULL, context );
400     if( returnedError )
401     {
402         return returnedError;
403     }
404
405     returnedError = CGLDestroyPixelFormat( pix );
406     if( returnedError )
407     {
408         return returnedError;
409     }
410
411     return kCGLNoError;
412 }
413
414 #define POT(V,N) V = 1; while( V < N ) { V <<= 1; }
415
416 int screen_DrawCursor( demux_sys_t *p_sys, CGPoint *cursor_pos )
417 {
418     int size;
419     int tmp1, tmp2, tmp3, tmp4;
420     unsigned char *cursor_image;
421
422     screen_data_t *p_data = p_sys->p_data;
423
424     int seed = CGSCurrentCursorSeed();
425     if( seed != p_data->cursor_seed )
426     {
427         p_data->cursor_need_update = 1;
428
429         if( CGSGetGlobalCursorDataSize( p_data->connection, &size )
430             != kCGErrorSuccess)
431         {
432             return VLC_EGENERIC;
433         }
434
435         cursor_image = ( unsigned char * )malloc( size );
436
437         if( CGSGetGlobalCursorData( p_data->connection,
438                                     cursor_image, &size,
439                                     &tmp1,
440                                     &p_data->cursor_rect, &p_data->cursor_hot,
441                                     &tmp2, &tmp3, &tmp4 )
442             != kCGErrorSuccess )
443         {
444             free( cursor_image );
445
446             return VLC_EGENERIC;
447         }
448
449         long int pot_width, pot_height;
450
451         POT( pot_width, p_data->cursor_rect.size.width );
452         POT( pot_height, p_data->cursor_rect.size.height );
453
454         p_data->cursor_texture_map_u =
455             p_data->cursor_rect.size.width / ( double )pot_width;
456         p_data->cursor_texture_map_v =
457             p_data->cursor_rect.size.height / ( double )pot_height;
458
459         /* We need transparent image larger than original,
460          * use calloc to clear alpha value to 0. */
461         char *pot_cursor_image = ( char * )calloc( pot_width * pot_height * 4, sizeof( char ) );
462         int width, height;
463         char *from, *to;
464
465         width = p_data->cursor_rect.size.width;
466         height = p_data->cursor_rect.size.height;
467
468         from = ( char * )cursor_image;
469         to = pot_cursor_image;
470
471 #ifdef __LITTLE_ENDIAN__
472         int y, fromwidth, towidth;
473
474         fromwidth = width * 4;
475         towidth = pot_width * 4;
476
477         for( y = height; y; y -- )
478         {
479             memcpy( to, from, fromwidth );
480             to += towidth;
481             from += fromwidth;
482         }
483 #else
484         int x, y, diff;
485         diff = ( pot_width - width ) * 4;
486         for( y = height; y; y -- )
487         {
488             for( x = width; x; x -- )
489             {
490                 to[0] = from[3];
491                 to[1] = from[2];
492                 to[2] = from[1];
493                 to[3] = from[0];
494
495                 to += 4;
496                 from += 4;
497             }
498
499             to += diff;
500         }
501 #endif
502
503         glEnable( GL_TEXTURE_2D );
504         glBindTexture( GL_TEXTURE_2D, p_data->cursor_texture );
505         glTexImage2D( GL_TEXTURE_2D, 0,
506                       GL_RGBA8,
507                       pot_width, pot_height, 0,
508                       GL_RGBA, GL_UNSIGNED_BYTE,
509                       pot_cursor_image );
510
511         p_data->cursor_need_update = 0;
512         p_data->cursor_seed = seed;
513
514         free( pot_cursor_image );
515         free( cursor_image );
516     }
517     else if( p_data->cursor_need_update )
518     {
519         return VLC_EGENERIC;
520     }
521
522     double x, y;
523     double x1, y1, x2, y2;
524
525     x = cursor_pos->x - p_sys->i_left - p_data->cursor_hot.x;
526     y = cursor_pos->y - p_sys->i_top - p_data->cursor_hot.y;
527
528     x1 = 2.0 * x / p_data->width - 1.0;
529     y1 = 2.0 * y / p_data->height - 1.0;
530     x2 = 2.0 * ( x + p_data->cursor_rect.size.width ) / p_data->width - 1.0;
531     y2 = 2.0 * ( y + p_data->cursor_rect.size.height ) / p_data->height - 1.0;
532
533     glColor3f( 1.0f, 1.0f, 1.0f );
534     glEnable( GL_TEXTURE_2D );
535     glEnable( GL_BLEND );
536     glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
537     glBindTexture( GL_TEXTURE_2D, p_data->cursor_texture );
538     glBegin( GL_POLYGON );
539     glTexCoord2f( 0.0, 0.0 );
540     glVertex2f( x1, y1 );
541     glTexCoord2f( p_data->cursor_texture_map_u, 0.0 );
542     glVertex2f( x2, y1 );
543     glTexCoord2f( p_data->cursor_texture_map_u, p_data->cursor_texture_map_v );
544     glVertex2f( x2, y2 );
545     glTexCoord2f( 0.0, p_data->cursor_texture_map_v );
546     glVertex2f( x1, y2 );
547     glEnd();
548     glDisable( GL_BLEND );
549     glDisable( GL_TEXTURE_2D );
550
551     return VLC_SUCCESS;
552 }
553
554 int screen_CaptureScreen( demux_sys_t *p_sys )
555 {
556     screen_data_t *p_data = p_sys->p_data;
557
558     if( p_data->myCGDisplayCreateImageForRect != NULL )
559     {
560         CGImageRef captured_image;
561         CGContextRef captured_bitmap;
562         CGRect capture_rect;
563         CGRect draw_rect;
564
565         capture_rect.origin.x = p_sys->i_left;
566         capture_rect.origin.y = p_sys->i_top;
567         capture_rect.size.width = p_data->width;
568         capture_rect.size.height = p_data->height;
569         captured_image =
570             p_data->myCGDisplayCreateImageForRect( p_data->display_id,
571                                                    capture_rect );
572
573         captured_bitmap =
574             CGBitmapContextCreate( ( unsigned char * )p_data->screen_image,
575                                    capture_rect.size.width,
576                                    capture_rect.size.height,
577                                    8,
578                                    capture_rect.size.width * 4,
579                                    CGColorSpaceCreateDeviceRGB(),
580                                    kCGImageAlphaPremultipliedLast );
581
582         draw_rect.size.width = CGImageGetWidth( captured_image );
583         draw_rect.size.height = CGImageGetHeight( captured_image );
584         draw_rect.origin.x = 0;
585         draw_rect.origin.y = capture_rect.size.height - draw_rect.size.height;
586         CGContextDrawImage( captured_bitmap, draw_rect, captured_image );
587
588         CGContextRelease( captured_bitmap );
589         CGImageRelease( captured_image );
590     }
591     else
592     {
593         CGLSetCurrentContext( p_data->screen );
594
595         glReadPixels( p_sys->i_left,
596                       p_data->screen_height - p_sys->i_top - p_data->height,
597                       p_data->width,
598                       p_data->height,
599                       GL_RGBA, GL_UNSIGNED_BYTE,
600                       p_data->screen_image );
601     }
602
603     return VLC_SUCCESS;
604 }