/* xlogin-rootjpeg.c	
	This version is (C) 2015, James Budiono
	Licensed as GNU GPL Version 2 or later.
	
	Original license:
	Load a jpeg into X servers root window
	(c) 2008 Jonathan Andrews

	Free Software, Use at own risk.
	This version is entirely free and may be used by anyone for anything, forked versions may replace this
	with a license of their chosing, but remember my copywrite must still stand.

	Use entirely at your own risk, no warrenty or fitness for purpse is stated or implied.

*/

char PROGNAME[80]="xlogin-rootjpeg\0";
char LASTCHANGED[80]="19 April 2015\0";
#define VERSION		"2.0"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_resize.h"

/* X11 Stuff */
#include <X11/Xlib.h>
#include "xlogin.h"

#define BLACK_BG "black" // pre-defined imagename

Display *display;
int screen;
int xdepth, xwidth, xheight;                                    // X Server dimensions
unsigned long window_mask;
XSetWindowAttributes window_attributes;
Window xcanvas;
GC image_gc;

inline static unsigned short make15color(unsigned char r, unsigned char g, unsigned char b)
{
    return (
	(((r >> 3) & 31) << 10) |
	(((g >> 3) & 31) << 5)  |
	 ((b >> 3) & 31)        );
}

inline static unsigned short make16color(unsigned char r, unsigned char g, unsigned char b)
{
    return (
	(((r >> 3) & 31) << 11) |
	(((g >> 2) & 63) << 5)  |
	 ((b >> 3) & 31)        );
}

// load and resize jpeg using stb_image
XImage *load_jpeg(char *imagename) {
	int width, height, channels;
	XImage *xim = NULL;
	char *data, *resized, *image, *buffer;;

	// load it
	image = data = stbi_load(imagename, &width, &height, &channels, 4); // RGBA always
	if (!image) {
		fprintf(stderr,"Cannot load %s: %s\n",imagename,stbi_failure_reason());
		return NULL;
	}
	// attempt to resize
	resized = malloc(xwidth * xheight * 4);
	if (stbir_resize_uint8(data, width, height, 0, resized, xwidth, xheight, 0, 4)) {
		width = xwidth; height = xheight;
		image = resized; // use resized version
	}

	if (xdepth == 16 || xdepth == 15) {
		buffer =  malloc (width * height * 2);
        xim=XCreateImage (display, CopyFromParent, xdepth, ZPixmap, 0, (char *) buffer, width, height, 16, width * 2);
        
	} else if (xdepth == 24 || xdepth == 32) {
		buffer =  malloc (width * height * 4);
		xim=XCreateImage (display, CopyFromParent, xdepth, ZPixmap, 0, (char *) buffer, width, height, 32, width * 4);
		
	} else {
		fprintf(stderr,"Bit depth not supported\n.");
		return NULL;
	}
	
	// load image to XImage backed memory
	int i;
	switch(xdepth) {
		case 15:		
			for (i = 0; i < width*height*4; i+=4)
			{
				unsigned short pixel = make15color(image[i+0], image[i+1], image[i+2]);
				buffer[(i>>1)+0] = pixel & 0xff;
				buffer[(i>>1)+1] = pixel >> 8;
			}
			break;
			
		case 16:
			for (i = 0; i < width*height*4; i+=4)
			{
				unsigned short pixel = make16color(image[i+0], image[i+1], image[i+2]);
				buffer[(i>>1)+0] = pixel & 0xff;
				buffer[(i>>1)+1] = pixel >> 8;
			}
			break;		
			
		case 24:
		case 32:
			for (i = 0; i < width*height*4; i+=4)
			{
				buffer[i+0] = image[i+2];
				buffer[i+1] = image[i+1];
				buffer[i+2] = image[i+0];
				buffer[i+3] = 0; // don't use the alpha channel
			}
			break;					
	}

	free(resized);
	stbi_image_free(data);
	return xim;	
}

char mcookie_txt[256]; // Magic cookie from xdmcpserver
char mcookie[16]; // MIT MAGIC COOKIE originally handed out by xdmcp-server
void load_xdmcp_cookies()
{
	int x;
	char *env;	// Explicit pointer type	
	
	// Copy the text version of the key from the environment
	env=getenv("XMAGIC");
	if (env!=NULL) {
		strncpy(mcookie_txt,getenv("XMAGIC"),sizeof(mcookie_txt)-1);
		mcookie_txt[sizeof(mcookie_txt)-1]=0;
		//fprintf(stderr,"%s: Have MIT MAGIC COOKIE %s\n\n",PROGNAME,mcookie_txt);
		
		char twodigits[3];
		int counter=0;
		for (x=0;x<sizeof(mcookie)*2 && x<strlen(mcookie_txt);x=x+2) {
			twodigits[0]=mcookie_txt[x];
			twodigits[1]=mcookie_txt[x+1];
			twodigits[2]='\0';

			sscanf(twodigits,"%02x",&mcookie[counter]);
			counter++;
		}

		// Almost undocumented but important call ! Tell the X server its magic number is the one i've generated
		XSetAuthorization((char *)"MIT-MAGIC-COOKIE-1", (int) strlen("MIT-MAGIC-COOKIE-1"), mcookie, (int) 16);		
	}
}

// ******************************************************************************************************************************

main(int argc, char *argv[])
{
	char *displayname=NULL;
	char *imagename=NULL;
	XImage *xibackground = NULL;
	int x;

	// option parsing
	for (x=1; x<argc; x++) {
		if (strcmp(argv[x],"-h")==0 ||
		    strcmp(argv[x],"-help")==0 ||
		    strcmp(argv[x],"--help")==0)
		{
			fprintf(stderr,"%s: <option list>\n",PROGNAME);
			fprintf(stderr,"-display <name> IP or Hostname of X server to attach to, default is $DISPLAY\n");
			fprintf(stderr,"-image <name> Filename of background image\n");
			exit(0);
		}
		else if (strcmp(argv[x],"-ver")==0 ||
		         strcmp(argv[x],"-version")==0 ||
		         strcmp(argv[x],"--version")==0)
		{
			fprintf(stderr,"%s version %s\n",PROGNAME,VERSION);
			exit(0);
		}
		else if (strcmp(argv[x],"-display")==0)
		{
			x++; 
			displayname=argv[x];
			fprintf(stderr,"%s: display is %s\n",PROGNAME,displayname);
		}
		else if (strcmp(argv[x],"-image")==0)
		{
			x++; 
			imagename=argv[x];
			fprintf(stderr,"%s: image is %s\n",PROGNAME,imagename);
		}
	}


	if (imagename)
	{
		// ********************************************************************************************************************************
		// Open X server connection
		load_xdmcp_cookies();
		fprintf(stderr,"%s: Opening display %s\n",PROGNAME,displayname);
		display = XOpenDisplay (displayname);
		if (display==NULL) {
			fprintf(stderr,"%s:\tFailed to open display %s\n",PROGNAME,displayname);
			sleep(6);
			exit(1);
		}

		// ********************************************************************************************************************************
		screen = DefaultScreen (display);
		xdepth = DefaultDepth (display, screen);
		window_attributes.border_pixel = BlackPixel (display, screen);
		window_attributes.background_pixel = BlackPixel (display, screen);
		window_attributes.override_redirect = 0;
		window_mask = CWBackPixel | CWBorderPixel;

		//No need to create the window, just get a handle on it
		xcanvas=RootWindow(display,screen);
		XStoreName (display,xcanvas,PROGNAME);                          // Tell window manager my name
		XMapWindow (display, xcanvas);
		image_gc = XCreateGC (display, xcanvas, 0L, 0);

		// ********************************************************************************************************************************
		// Get the details for Xserver root window - then clear it to black. Some X servers clear to black by themselves, some dont
		xwidth=DisplayWidth(display,DefaultScreen(display));
		xheight=DisplayHeight(display,DefaultScreen(display));


       	// ********************************************************************************************************************************
		// A filename of black is a special case, dont load an image just make the root window black and exit
		if (strcmp(imagename,BLACK_BG)==0)
		{
        	XSetFillStyle(display, image_gc, FillSolid);
        	XSetForeground(display, image_gc, 0x00000000);
			XFillRectangle(display, xcanvas, image_gc, 0,0, xwidth, xheight);
			XFlush(display);
			XCloseDisplay(display);
			fprintf(stderr,"%s: Closed display ok \n",PROGNAME); 
			exit(0);
		}
		
		xibackground = load_jpeg(imagename);
		if (xibackground) {
			XPutImage (display, xcanvas, image_gc, xibackground, 0, 0, 0, 0,xibackground->width, xibackground->height);
			XFlush(display);			
		}

		XCloseDisplay(display);
		fprintf(stderr,"%s: Closed display ok \n",PROGNAME); 
	}
	else
		fprintf(stderr,"%s: please specify -image <path/filename>\n", PROGNAME);

}

