Commit 1cc498b2 authored by Felix Paul Kühne's avatar Felix Paul Kühne

screen/mac: clean-up, improve and partially rewrite the code

TODO: cursor rendering
parent fbf37518
...@@ -29,49 +29,23 @@ ...@@ -29,49 +29,23 @@
*****************************************************************************/ *****************************************************************************/
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
# import "config.h" # include "config.h"
#endif #endif
#import <vlc_common.h> #import <vlc_common.h>
#import <vlc_block.h>
// Fix ourselves ColorSync headers that gets included in ApplicationServices. #import "screen.h"
#define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
#define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
#define __MACHINEEXCEPTIONS__
#import <ApplicationServices/ApplicationServices.h>
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl.h>
#import <stdlib.h>
typedef int CGSConnectionRef;
extern CGError CGSNewConnection( void *, CGSConnectionRef * );
extern CGError CGSReleaseConnection( CGSConnectionRef );
extern CGError CGSGetGlobalCursorDataSize( CGSConnectionRef, int * );
extern CGError CGSGetGlobalCursorData( CGSConnectionRef, unsigned char *,
int *, int *, CGRect *, CGPoint *,
int *, int *, int * );
extern CGError CGSGetCurrentCursorLocation( CGSConnectionRef, CGPoint * );
extern int CGSCurrentCursorSeed( void );
typedef CGImageRef ( *typeofCGDisplayCreateImageForRect )( CGDirectDisplayID displayID, CGRect rect ); #import <ApplicationServices/ApplicationServices.h>
#import <QuartzCore/QuartzCore.h>
#import "screen.h" extern int CGSMainConnectionID();
extern CGImageRef CGSCreateRegisteredCursorImage(int, char*, CGPoint*);
struct screen_data_t struct screen_data_t
{ {
CGLContextObj screen; block_t *p_block;
char *screen_image;
CGLContextObj clipped;
char *clipped_image;
GLuint cursor_texture;
int cursor_seed;
bool cursor_need_update;
CGRect cursor_rect;
CGPoint cursor_hot;
double cursor_texture_map_u, cursor_texture_map_v;
int width; int width;
int height; int height;
...@@ -81,88 +55,45 @@ struct screen_data_t ...@@ -81,88 +55,45 @@ struct screen_data_t
int screen_width; int screen_width;
int screen_height; int screen_height;
CGSConnectionRef connection;
CFBundleRef bundle;
typeofCGDisplayCreateImageForRect myCGDisplayCreateImageForRect;
CGDirectDisplayID display_id; CGDirectDisplayID display_id;
}; };
CGLError screen_CreateContext( CGLContextObj *context, int screen_InitCapture(demux_t *p_demux)
CGLPixelFormatAttribute a0,
CGLPixelFormatAttribute a1,
CGLPixelFormatAttribute a2,
CGLPixelFormatAttribute a3 );
int screen_DrawCursor( demux_sys_t *p_sys, CGPoint *cursor_pos );
int screen_CaptureScreen( demux_sys_t *p_sys );
int screen_InitCapture( demux_t *p_demux )
{ {
demux_sys_t *p_sys = p_demux->p_sys; demux_sys_t *p_sys = p_demux->p_sys;
screen_data_t *p_data; screen_data_t *p_data;
CGLError returnedError; CGLError returnedError;
unsigned int i; int i_bits_per_pixel, i_chroma = 0;
p_sys->p_data = p_data = p_sys->p_data = p_data = calloc(1, sizeof(screen_data_t ) );
( screen_data_t * )calloc( 1, sizeof( screen_data_t ) ); if (!p_data)
return VLC_ENOMEM;
/* fetch the screen we should capture */
p_data->display_id = kCGDirectMainDisplay; p_data->display_id = kCGDirectMainDisplay;
unsigned int displayCount; unsigned int displayCount = 0;
displayCount = 0; returnedError = CGGetOnlineDisplayList(0, NULL, &displayCount);
returnedError = CGGetOnlineDisplayList( 0, NULL, &displayCount ); if (!returnedError) {
if( !returnedError )
{
CGDirectDisplayID *ids; CGDirectDisplayID *ids;
ids = ( CGDirectDisplayID * )malloc( displayCount * sizeof( CGDirectDisplayID ) ); ids = ( CGDirectDisplayID * )malloc( displayCount * sizeof( CGDirectDisplayID ) );
returnedError = CGGetOnlineDisplayList( displayCount, ids, &displayCount ); returnedError = CGGetOnlineDisplayList( displayCount, ids, &displayCount );
if( !returnedError ) if (!returnedError) {
{ if (p_sys->i_display_id > 0) {
if ( p_sys->i_display_id > 0 ) for (unsigned int i = 0; i < displayCount; i++) {
{ if (p_sys->i_display_id == ids[i]) {
for( i = 0; i < displayCount; i ++ )
{
if( p_sys->i_display_id == ids[i] )
{
p_data->display_id = ids[i]; p_data->display_id = ids[i];
break; break;
} }
} }
} } else if (p_sys->i_screen_index > 0 && p_sys->i_screen_index <= displayCount)
else if ( p_sys->i_screen_index > 0 && p_sys->i_screen_index <= displayCount )
{
p_data->display_id = ids[p_sys->i_screen_index - 1]; p_data->display_id = ids[p_sys->i_screen_index - 1];
}
} }
free( ids ); free(ids);
} }
/* CGImage Function /* Get the device context for the whole screen */
* CGDisplayCreateImageForRect is available in Mac OS X v10.6 and later */ CGRect rect = CGDisplayBounds(p_data->display_id);
p_data->myCGDisplayCreateImageForRect = NULL;
CFURLRef frameworkURL = NULL;
CFStringRef path = CFSTR( "file://localhost/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework" );
frameworkURL = CFURLCreateWithString( kCFAllocatorDefault, path, NULL );
if( frameworkURL != NULL )
{
p_data->bundle = CFBundleCreate( kCFAllocatorDefault, frameworkURL );
if( p_data->bundle != NULL )
{
p_data->myCGDisplayCreateImageForRect =
( typeofCGDisplayCreateImageForRect )CFBundleGetFunctionPointerForName
( p_data->bundle, CFSTR( "CGDisplayCreateImageForRect" ) );
}
CFRelease( frameworkURL );
}
/* Screen Size */
CGRect rect = CGDisplayBounds( p_data->display_id );
p_data->screen_left = rect.origin.x; p_data->screen_left = rect.origin.x;
p_data->screen_top = rect.origin.y; p_data->screen_top = rect.origin.y;
p_data->screen_width = rect.size.width; p_data->screen_width = rect.size.width;
...@@ -170,437 +101,143 @@ int screen_InitCapture( demux_t *p_demux ) ...@@ -170,437 +101,143 @@ int screen_InitCapture( demux_t *p_demux )
p_data->width = p_sys->i_width; p_data->width = p_sys->i_width;
p_data->height = p_sys->i_height; p_data->height = p_sys->i_height;
if( p_data->width <= 0 || p_data->height <= 0 ) if( p_data->width <= 0 || p_data->height <= 0 ) {
{
p_data->width = p_data->screen_width; p_data->width = p_data->screen_width;
p_data->height = p_data->screen_height; p_data->height = p_data->screen_height;
} }
/* Screen Context */ CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(CGDisplayCopyDisplayMode(p_data->display_id));
int length = CFStringGetLength(pixelEncoding);
if( p_data->myCGDisplayCreateImageForRect == NULL ) length++;
{ char *psz_name = (char *)malloc(length);
returnedError = CFStringGetCString(pixelEncoding, psz_name, length, kCFStringEncodingUTF8);
screen_CreateContext( &p_data->screen, msg_Dbg(p_demux, "pixel encoding is '%s'", psz_name);
kCGLPFAFullScreen, CFRelease(pixelEncoding);
kCGLPFADisplayMask,
( CGLPixelFormatAttribute )CGDisplayIDToOpenGLDisplayMask( p_data->display_id ), if (!strcmp(psz_name, IO32BitDirectPixels)) {
( CGLPixelFormatAttribute )0 ); i_chroma = VLC_CODEC_RGB32;
if( returnedError ) i_bits_per_pixel = 32;
goto errorHandling; } else if (!strcmp(psz_name, IO16BitDirectPixels)) {
i_chroma = VLC_CODEC_RGB16;
returnedError = CGLSetCurrentContext( p_data->screen ); i_bits_per_pixel = 16;
if( returnedError ) } else if (!strcmp(psz_name, IO8BitIndexedPixels)) {
goto errorHandling; i_chroma = VLC_CODEC_RGB8;
i_bits_per_pixel = 8;
returnedError = CGLSetFullScreen( p_data->screen ); } else {
if( returnedError ) msg_Err(p_demux, "unsupported pixel encoding");
goto errorHandling; free(p_data);
} return VLC_EGENERIC;
/* Clipped Context */
returnedError =
screen_CreateContext( &p_data->clipped,
kCGLPFAOffScreen,
kCGLPFAColorSize,
( CGLPixelFormatAttribute )32,
( CGLPixelFormatAttribute )0 );
if( returnedError )
goto errorHandling;
returnedError = CGLSetCurrentContext( p_data->clipped );
if( returnedError )
goto errorHandling;
/* Clipped Image */
p_data->clipped_image =
( char * )malloc( p_data->width * p_data->height * 4 );
returnedError = CGLSetOffScreen( p_data->clipped, p_data->width, p_data->height, p_data->width * 4, p_data->clipped_image );
if( returnedError )
goto errorHandling;
/* Screen Image */
if( p_data->myCGDisplayCreateImageForRect != NULL )
{
p_data->screen_image =
( char * )malloc( p_data->screen_width * p_data->screen_height * 4 );
} }
else free(psz_name);
{
p_data->screen_image = /* setup format */
( char * )malloc( p_data->width * p_data->height * 4 ); es_format_Init(&p_sys->fmt, VIDEO_ES, i_chroma );
p_sys->fmt.video.i_visible_width =
p_sys->fmt.video.i_width = rect.size.width;
p_sys->fmt.video.i_visible_height =
p_sys->fmt.video.i_height = rect.size.height;
p_sys->fmt.video.i_bits_per_pixel = i_bits_per_pixel;
p_sys->fmt.video.i_chroma = i_chroma;
switch (i_chroma) {
case VLC_CODEC_RGB15:
p_sys->fmt.video.i_rmask = 0x7c00;
p_sys->fmt.video.i_gmask = 0x03e0;
p_sys->fmt.video.i_bmask = 0x001f;
break;
case VLC_CODEC_RGB24:
p_sys->fmt.video.i_rmask = 0x00ff0000;
p_sys->fmt.video.i_gmask = 0x0000ff00;
p_sys->fmt.video.i_bmask = 0x000000ff;
break;
case VLC_CODEC_RGB32:
p_sys->fmt.video.i_rmask = 0x00ff0000;
p_sys->fmt.video.i_gmask = 0x0000ff00;
p_sys->fmt.video.i_bmask = 0x000000ff;
break;
default:
msg_Warn( p_demux, "Unknown RGB masks" );
break;
} }
/* Cursor */
CGSNewConnection( NULL, &( p_data->connection ) );
p_data->cursor_need_update = 1;
p_data->cursor_seed = 0;
glGenTextures( 1, &( p_data->cursor_texture ) );
glBindTexture( GL_TEXTURE_2D, p_data->cursor_texture );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
/* System */
es_format_Init( &p_sys->fmt, VIDEO_ES, VLC_CODEC_RGB32 );
/* p_sys->fmt.video.i_* must set to screen size, not subscreen size */
p_sys->fmt.video.i_width = p_data->screen_width;
p_sys->fmt.video.i_visible_width = p_data->screen_width;
p_sys->fmt.video.i_height = p_data->screen_height;
p_sys->fmt.video.i_bits_per_pixel = 32;
return VLC_SUCCESS; return VLC_SUCCESS;
errorHandling:
msg_Err( p_demux, "Core OpenGL failure: %s", CGLErrorString( returnedError ) );
return VLC_EGENERIC;
} }
int screen_CloseCapture( demux_t *p_demux ) int screen_CloseCapture(demux_t *p_demux)
{ {
screen_data_t *p_data = ( screen_data_t * )p_demux->p_sys->p_data; demux_sys_t *p_sys = p_demux->p_sys;
screen_data_t *p_data = p_sys->p_data;
CGLSetCurrentContext( NULL );
/* Cursor */
glBindTexture( GL_TEXTURE_2D, 0 );
glDeleteTextures( 1, &( p_data->cursor_texture ) );
CGSReleaseConnection( p_data->connection );
/* Screen Image */
if( p_data->screen_image != NULL )
{
free( p_data->screen_image );
p_data->screen_image = NULL;
}
/* Clipped Image */
if( p_data->clipped_image != NULL )
{
free( p_data->clipped_image );
p_data->clipped_image = NULL;
}
/* Clipped Context */
CGLClearDrawable( p_data->clipped );
CGLDestroyContext( p_data->clipped );
/* Screen Context */
if( p_data->myCGDisplayCreateImageForRect == NULL )
{
CGLClearDrawable( p_data->screen );
CGLDestroyContext( p_data->screen );
}
/* CGImage */
CFRelease( p_data->bundle ); if (p_data->p_block)
block_Release(p_data->p_block);
free( p_data ); free(p_data);
return VLC_SUCCESS; return VLC_SUCCESS;
} }
block_t *screen_Capture( demux_t *p_demux ) block_t *screen_Capture(demux_t *p_demux)
{ {
demux_sys_t *p_sys = p_demux->p_sys; demux_sys_t *p_sys = p_demux->p_sys;
screen_data_t *p_data = ( screen_data_t * )p_sys->p_data; screen_data_t *p_data = ( screen_data_t * )p_sys->p_data;
block_t *p_block; block_t *p_block;
int i_size; CGRect capture_rect;
CGImageRef image;
i_size = p_sys->fmt.video.i_height * p_sys->fmt.video.i_width * 4; CGDataProviderRef dataProvider;
CFDataRef data;
if( !( p_block = block_Alloc( i_size ) ) )
{
msg_Warn( p_demux, "cannot get block" );
return NULL;
}
/* forward cursor location */
CGPoint cursor_pos; CGPoint cursor_pos;
CGError cursor_result;
cursor_pos.x = 0;
cursor_pos.y = 0;
cursor_result = CGEventRef event = CGEventCreate(NULL);
CGSGetCurrentCursorLocation( p_data->connection, &cursor_pos ); cursor_pos = CGEventGetLocation(event);
CFRelease(event);
cursor_pos.x -= p_data->screen_left; cursor_pos.x -= p_data->screen_left;
cursor_pos.y -= p_data->screen_top; cursor_pos.y -= p_data->screen_top;
if( p_sys->b_follow_mouse if (p_sys->b_follow_mouse)
&& cursor_result == kCGErrorSuccess ) FollowMouse(p_sys, cursor_pos.x, cursor_pos.y);
{
FollowMouse( p_sys, cursor_pos.x, cursor_pos.y ); capture_rect.origin.x = p_sys->i_left;
} capture_rect.origin.y = p_sys->i_top;
capture_rect.size.width = p_data->width;
screen_CaptureScreen( p_sys ); capture_rect.size.height = p_data->height;
CGLSetCurrentContext( p_data->clipped ); #if 0
// FIXME: actually plot cursor image into snapshot
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); /* fetch cursor image */
glClear( GL_COLOR_BUFFER_BIT ); CGImageRef cursor_image;
int cid = CGSMainConnectionID();
glDrawPixels( p_data->width, CGPoint outHotSpot;
p_data->height, cursor_image = CGSCreateRegisteredCursorImage(cid, (char *)"com.apple.coregraphics.GlobalCurrent", &outHotSpot);
GL_RGBA, GL_UNSIGNED_BYTE, p_data->screen_image );
if( cursor_result == kCGErrorSuccess )
{
screen_DrawCursor( p_sys, &cursor_pos );
}
glReadPixels( 0, 0,
p_data->width,
p_data->height,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
p_block->p_buffer );
return p_block;
}
CGLError screen_CreateContext( CGLContextObj *context,
CGLPixelFormatAttribute a0,
CGLPixelFormatAttribute a1,
CGLPixelFormatAttribute a2,
CGLPixelFormatAttribute a3 )
{
CGLPixelFormatAttribute attribs[4];
CGLPixelFormatObj pix;
GLint npix;
CGLError returnedError;
attribs[0] = a0;
attribs[1] = a1;
attribs[2] = a2;
attribs[3] = a3;
returnedError = CGLChoosePixelFormat( attribs, &pix, &npix );
if( returnedError )
{
return returnedError;
}
returnedError = CGLCreateContext( pix, NULL, context );
if( returnedError )
{
return returnedError;
}
returnedError = CGLDestroyPixelFormat( pix );
if( returnedError )
{
return returnedError;
}
return kCGLNoError;
}
#define POT(V,N) V = 1; while( V < N ) { V <<= 1; }
int screen_DrawCursor( demux_sys_t *p_sys, CGPoint *cursor_pos )
{
int size;
int tmp1, tmp2, tmp3, tmp4;
unsigned char *cursor_image;
screen_data_t *p_data = p_sys->p_data;
int seed = CGSCurrentCursorSeed();
if( seed != p_data->cursor_seed )
{
p_data->cursor_need_update = 1;
if( CGSGetGlobalCursorDataSize( p_data->connection, &size )
!= kCGErrorSuccess)
{
return VLC_EGENERIC;
}
cursor_image = ( unsigned char * )malloc( size );
if( CGSGetGlobalCursorData( p_data->connection,
cursor_image, &size,
&tmp1,
&p_data->cursor_rect, &p_data->cursor_hot,
&tmp2, &tmp3, &tmp4 )
!= kCGErrorSuccess )
{
free( cursor_image );
return VLC_EGENERIC;
}
long int pot_width, pot_height;
POT( pot_width, p_data->cursor_rect.size.width );
POT( pot_height, p_data->cursor_rect.size.height );
p_data->cursor_texture_map_u =
p_data->cursor_rect.size.width / ( double )pot_width;
p_data->cursor_texture_map_v =
p_data->cursor_rect.size.height / ( double )pot_height;
/* We need transparent image larger than original,
* use calloc to clear alpha value to 0. */
char *pot_cursor_image = ( char * )calloc( pot_width * pot_height * 4, sizeof( char ) );
int width, height;
char *from, *to;
width = p_data->cursor_rect.size.width;
height = p_data->cursor_rect.size.height;
from = ( char * )cursor_image;
to = pot_cursor_image;
#ifdef __LITTLE_ENDIAN__
int y, fromwidth, towidth;
fromwidth = width * 4;
towidth = pot_width * 4;
for( y = height; y; y -- )
{
memcpy( to, from, fromwidth );
to += towidth;
from += fromwidth;
}
#else
int x, y, diff;
diff = ( pot_width - width ) * 4;
for( y = height; y; y -- )
{
for( x = width; x; x -- )
{
to[0] = from[3];
to[1] = from[2];
to[2] = from[1];
to[3] = from[0];
to += 4;
from += 4;
}
to += diff;
}
#endif #endif
glEnable( GL_TEXTURE_2D ); /* fetch image data */
glBindTexture( GL_TEXTURE_2D, p_data->cursor_texture ); image = CGDisplayCreateImageForRect(p_data->display_id, capture_rect);
glTexImage2D( GL_TEXTURE_2D, 0, if (image) {
GL_RGBA8, /* build block */
pot_width, pot_height, 0, int i_buffer = (p_sys->fmt.video.i_bits_per_pixel + 7) / 8 * p_sys->fmt.video.i_width * p_sys->fmt.video.i_height;
GL_RGBA, GL_UNSIGNED_BYTE, p_block = block_Alloc(i_buffer);
pot_cursor_image ); if (!p_block) {
msg_Warn(p_demux, "can't get block");
p_data->cursor_need_update = 0; return NULL;
p_data->cursor_seed = seed; }
free( pot_cursor_image ); dataProvider = CGImageGetDataProvider(image);
free( cursor_image ); data = CGDataProviderCopyData(dataProvider);
} CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), p_block->p_buffer);
else if( p_data->cursor_need_update )
{
return VLC_EGENERIC;
}
double x, y; CFRelease(data);
double x1, y1, x2, y2; CFRelease(dataProvider);
CFRelease(image);
x = cursor_pos->x - p_sys->i_left - p_data->cursor_hot.x;
y = cursor_pos->y - p_sys->i_top - p_data->cursor_hot.y;
x1 = 2.0 * x / p_data->width - 1.0;
y1 = 2.0 * y / p_data->height - 1.0;
x2 = 2.0 * ( x + p_data->cursor_rect.size.width ) / p_data->width - 1.0;
y2 = 2.0 * ( y + p_data->cursor_rect.size.height ) / p_data->height - 1.0;
glColor3f( 1.0f, 1.0f, 1.0f );
glEnable( GL_TEXTURE_2D );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glBindTexture( GL_TEXTURE_2D, p_data->cursor_texture );
glBegin( GL_POLYGON );
glTexCoord2f( 0.0, 0.0 );
glVertex2f( x1, y1 );
glTexCoord2f( p_data->cursor_texture_map_u, 0.0 );
glVertex2f( x2, y1 );
glTexCoord2f( p_data->cursor_texture_map_u, p_data->cursor_texture_map_v );
glVertex2f( x2, y2 );
glTexCoord2f( 0.0, p_data->cursor_texture_map_v );
glVertex2f( x1, y2 );
glEnd();
glDisable( GL_BLEND );
glDisable( GL_TEXTURE_2D );
return VLC_SUCCESS;
}
int screen_CaptureScreen( demux_sys_t *p_sys ) free(p_sys->p_mouse);
{
screen_data_t *p_data = p_sys->p_data;
if( p_data->myCGDisplayCreateImageForRect != NULL ) return p_block;
{
CGImageRef captured_image;
CGContextRef captured_bitmap;
CGRect capture_rect;
CGRect draw_rect;
capture_rect.origin.x = p_sys->i_left;
capture_rect.origin.y = p_sys->i_top;
capture_rect.size.width = p_data->width;
capture_rect.size.height = p_data->height;
captured_image =
p_data->myCGDisplayCreateImageForRect( p_data->display_id,
capture_rect );
captured_bitmap =
CGBitmapContextCreate( ( unsigned char * )p_data->screen_image,
capture_rect.size.width,
capture_rect.size.height,
8,
capture_rect.size.width * 4,
CGColorSpaceCreateDeviceRGB(),
kCGImageAlphaPremultipliedLast );
draw_rect.size.width = CGImageGetWidth( captured_image );
draw_rect.size.height = CGImageGetHeight( captured_image );
draw_rect.origin.x = 0;
draw_rect.origin.y = capture_rect.size.height - draw_rect.size.height;
CGContextDrawImage( captured_bitmap, draw_rect, captured_image );
CGContextRelease( captured_bitmap );
CGImageRelease( captured_image );
}
else
{
CGLSetCurrentContext( p_data->screen );
glReadPixels( p_sys->i_left,
p_data->screen_height - p_sys->i_top - p_data->height,
p_data->width,
p_data->height,
GL_RGBA, GL_UNSIGNED_BYTE,
p_data->screen_image );
} }
return VLC_SUCCESS; msg_Warn(p_demux, "no image!");
return NULL;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment