Fireplace / FPWindowManager.m

#import <X11/Xlib.h>
#import <X11/extensions/Xcomposite.h>
#import <X11/extensions/Xfixes.h>
#import <X11/extensions/shape.h>
#import <GL/gl.h>
#import <unistd.h>
#import <fcntl.h>
#import "FPWindowManager.h"
#import "FPX11Display.h"
#import "FPManagedWindow.h"

#define VBOXCOMPAT 0 // virtualbox and xephyr compatibility
#define USE_TEXTURE_RECTANGLE 0

static int attrListSgl[] = {
    GLX_RGBA, GLX_RED_SIZE, 4,
    GLX_GREEN_SIZE, 4,
    GLX_BLUE_SIZE, 4,
    GLX_DEPTH_SIZE, 16,
    None};

static int attrListDbl[] = { 
    GLX_RGBA, GLX_DOUBLEBUFFER,
    GLX_RED_SIZE, 4,
    GLX_GREEN_SIZE, 4,
    GLX_BLUE_SIZE, 4,
    GLX_DEPTH_SIZE, 16,
    None };

static const int fbconfig[] = {
    GLX_BIND_TO_TEXTURE_RGBA_EXT, True,
#if !VBOXCOMPAT
    GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
#endif
#if USE_TEXTURE_RECTANGLE
    GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_RECTANGLE_BIT_EXT,
#else
    GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
#endif
    GLX_Y_INVERTED_EXT, GLX_DONT_CARE,
    None
};

@implementation FPWindowManager
-(id)init
{
	FPX11Display * mainDisplay;
	Display * display;

	self = [super init];
	if(!self)
		return nil;

	_managedWindows = [NSMutableArray new];
	
	mainDisplay = [FPX11Display mainDisplay];	
	display = [mainDisplay display];
	

	[self _openStageWindow];

	XMapRaised(display, _stage);

//	XCompositeRedirectSubwindows (display, _root, CompositeRedirectAutomatic);
	XCompositeRedirectSubwindows(display, _root, CompositeRedirectManual);
	XSelectInput (display, _root, SubstructureNotifyMask);

	_overlay = XCompositeGetOverlayWindow (display, _root);

	XReparentWindow (display, _stage, _overlay, 0, 0);
//	XSelectInput (display, _stage, ExposureMask);

	[self _allowInputPassthrough:_stage];
	[self _allowInputPassthrough:_overlay];
	
	[[FPEventLoop mainLoop] addEventHandler:self];
	return self;
}

-(void)dealloc
{
	FPX11Display * mainDisplay = [FPX11Display mainDisplay];	
	Display * display = [mainDisplay display];

	XFree(_glxFBConfigs);
	glXMakeCurrent(display, None, NULL);
	glXDestroyContext(display, _glxContext);
	XDestroyWindow(display, _stage);
	XCompositeReleaseOverlayWindow(display, _overlay);
	[_managedWindows release];

	[[FPEventLoop mainLoop] removeEventHandler:self];
	[super dealloc];
}
-(void)_execCommand:(NSString*)command
{
	FPX11Display * mainDisplay = [FPX11Display mainDisplay];	
	Display * display = [mainDisplay display];
	if ((fcntl(ConnectionNumber(display), F_SETFD, 1)) == -1)
		NSLog(@"launching process: child cannot disinherit TCP fd");
	if(fork()==0)
	{
		if(execl([command UTF8String], [[command lastPathComponent] UTF8String], nil) == -1)	
		{
			NSLog(@"error launching process");
		}
		exit(0);
	}	
}
-(void)_allowInputPassthrough:(Window)window
{
	FPX11Display * mainDisplay = [FPX11Display mainDisplay];	
	Display * display = [mainDisplay display];

	XserverRegion region = XFixesCreateRegion (display, NULL, 0);
 
	XFixesSetWindowShapeRegion (display, window, ShapeBounding, 0, 0, 0);
	XFixesSetWindowShapeRegion (display, window, ShapeInput, 0, 0, region);

	XFixesDestroyRegion (display, region);
}
-(void)_openStageWindow
{
	// opens a GLX window ;)

	FPX11Display * mainDisplay = [FPX11Display mainDisplay];
	Display * display = [mainDisplay display];
	int screen = [mainDisplay screen];
	int width = [mainDisplay width];
	int height = [mainDisplay height];
	int depth = [mainDisplay depth];
	Visual * visual = [mainDisplay visual];
	Window rootWindow = [mainDisplay rootWindow];
	int borderWidth = 0;
	XSetWindowAttributes attr = {0};

#pragma unused(width)
#pragma unused(height)
#pragma unused(rootWindow)
#pragma unused(depth)
#pragma unused(visual)

	XVisualInfo *glxVisualInfo;
	Colormap glxColorMap;
	Window glxRootWindow;
	int glxWidth, glxHeight, glxDepth;
	Visual * glxVisual;
	
	////////	
	glxVisualInfo = _visualInfo = glXChooseVisual(display, screen, attrListDbl);
	if (glxVisualInfo == NULL)
	{
		glxVisualInfo = glXChooseVisual(display, screen, attrListSgl);
		_isVisualDoubleBuffered = NO;
		if(!glxVisualInfo)
		{
			[NSException raise:@"FPWindowManagerNoGLXVisual" format:@"Failed to get a GLX visual"];
		}
		NSLog(@"Note: only Singlebuffered Visual available!");
	}
	else
	{
		_isVisualDoubleBuffered = YES;
	}

	glxRootWindow = _root = [mainDisplay rootWindowForScreen:glxVisualInfo->screen];
	glxVisual = glxVisualInfo->visual;
	glxColorMap = XCreateColormap(
		display, 
		glxRootWindow, 
		glxVisual, 
		AllocNone);
	glxWidth = [mainDisplay widthForScreen:glxVisualInfo->screen];
	glxHeight = [mainDisplay heightForScreen:glxVisualInfo->screen];
	glxDepth = glxVisualInfo->depth;

	attr.colormap = glxColorMap;
	attr.border_pixel = 0;
	attr.event_mask = 
		StructureNotifyMask | FocusChangeMask | PointerMotionMask
                  | KeyPressMask | KeyReleaseMask | ButtonPressMask
                  | ButtonReleaseMask | PropertyChangeMask | ExposureMask;
	attr.override_redirect = true;

	_glxContext = glXCreateContext(display, glxVisualInfo, 0, GL_TRUE);

	///
	
	_stage = XCreateWindow(
		display, 
		_root,
		0, 0,
		glxWidth, glxHeight,
		borderWidth,
		glxDepth,
		InputOutput, // class
		glxVisual,
		CWBorderPixel | CWColormap | CWEventMask, // valuemask
		&attr
		);

	XSetStandardProperties(
		display, 
		_stage, 
		"Fireplace",
		"Fireplace",
		None, 
		NULL, 
		0, 
		NULL);

	glXMakeCurrent(display, _stage, _glxContext);
	
	///////////
		
	int c = 0;
	_glxFBConfigs = glXChooseFBConfig(display, screen, fbconfig, &c);
	if(!_glxFBConfigs)
	{
		// TODO: throw exception or return nil instead of exiting
		NSLog(@"No FBConfigs");
		exit(1);
	}
	
	///////////
	
	XGetGeometry(
		display, 
		_stage, 
		&_root, // dummy
		&_x, 
		&_y,
		&_width,
		&_height,
		&_borderWidth,
		&_depth);
	if(!glXIsDirect(display, _glxContext))
	{
		NSLog(@"No direct rendering");
	}
	_wasExposed = NO;
}

-(void)handleXEvent:(XEvent)event
{
	NSLog(@"Event %d", event.type);
	switch(event.type)	
	{
		case Expose:
		if (event.xexpose.count != 0)
			break;
		NSLog(@"Expose");
		[self render];
		if(!_wasExposed)
		{
			[self _execCommand:@"/usr/bin/xterm"];
		}
		_wasExposed = YES;
		break;

		case ConfigureNotify:
		NSLog(@"ConfigureNotify");
		break;
		
		case ClientMessage:
		NSLog(@"ClientMessage");
		break;
		
		case MapNotify:
		NSLog(@"Map notify");
		break;

		case ReparentNotify:
		NSLog(@"Reparent notify");
		break;

		case CreateNotify:
		NSLog(@"Create notify");
		if(event.xcreatewindow.window != _stage && event.xcreatewindow.window != _root && event.xcreatewindow.window != _overlay)
		{
			FPManagedWindow * managedWindow = [[FPManagedWindow alloc] initWithWindow:event.xcreatewindow.window manager:self];
			[_managedWindows addObject:managedWindow];
			[managedWindow release];
		}
		break;
	}
}

-(void)render
{
	glXMakeContextCurrent ([[FPX11Display mainDisplay] display], _stage, _stage, _glxContext);
	XSync([[FPX11Display mainDisplay] display], False);
	glViewport(0, 0, _width, _height);
	glClearColor(0.2,0.2,0.4,1);
	glClear(GL_COLOR_BUFFER_BIT);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, _width, _height, 0, 0, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	for(FPManagedWindow *managedWindow in _managedWindows)
	{
		[managedWindow drawWindow];		
	}

#if 0
	glLoadIdentity();
	glDisable(GL_TEXTURE_2D);
	glPointSize(10);
	glColor3f(1,0,0);
	glBegin(GL_POINTS);
	glVertex3f(rand()%640, rand()%480, 0);	
	glEnd();
#endif	

	if(_isVisualDoubleBuffered)
	{
		glXSwapBuffers([[FPX11Display mainDisplay] display], _stage);
	}
	glFinish();	
	glXWaitGL();
	
	/* vsync from KWin's scene_opengl in kdebase-3.90.1 */
#if VSYNC
	unsigned int sync;
        glFlush();
        glXGetVideoSync( &sync );
        glXWaitVideoSync( 2, ( sync + 1 ) % 2, &sync );
#endif
	/* end vsync */
}

/*
-(BOOL)_setupTexture
{
	int textureData[] = {
		255, 0, 0,     255, 255, 255,
		255, 255, 255, 0, 0, 0
	};

	GLuint _width = 2, _height = 2;
	GLuint _internalFormat = GL_RGBA;
        glGenTextures(1, &testTexture);

        GLuint errorID=glGetError();
        if(errorID != GL_NO_ERROR)
        {
                printf("GL ERROR: %d\n", (errorID));
                return NO;
        }
        glBindTexture(GL_TEXTURE_2D, testTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, _internalFormat, (GLsizei)_width, (GLsizei)_height, 0, _internalFormat, GL_UNSIGNED_BYTE, textureData);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	return YES;	
}
*/

-(void)updateWithDeltaTime:(float)sec;
{

	if(_wasExposed)
		[self render];

}

-(void)stopManagingWindow:(FPManagedWindow*)managedWindow
{
	[_managedWindows removeObject:managedWindow];
}

-(XVisualInfo*)visualInfo
{
	return _visualInfo;
}
-(GLXFBConfig*)glxFBConfigs
{
	return _glxFBConfigs;
}
-(GLXContext)glxContext
{
	return _glxContext;
}
-(Window)stage
{
	return _stage;
}
@end


// TODO: study http://sidvind.com/wiki/Xlib_and_GLX:_Part_1
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.