/* Xorgautoconfig: versatile xorg.conf creation tool for PowerPC
 *
 * Copyright (C) 2005      Lars Weiler <pylon@gentoo.org>
 *
 * This xorg.conf creation tool is a fork from Xautoconfig-0.23 
 * (http://ftp.penguinppc.org/projects/xautocfg/)
 *
 * The original header-comment contained:
 * Copyright (C) 2002-2004 Dan Burcaw <dan@ydl.net>
 * Copyright (C) 1999-2001 Tom Rini <trini@kernel.crashing.org>
 *
 * Thanks for this good start for a X.Org configurator!
 *
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <netinet/in.h>
#include <linux/fb.h>
#include <byteswap.h>
#include <dirent.h>

#include "ddcprobe/common.h"
#include "ddcprobe/vesamode.h"
#include "Xorgtext.h"

extern int bus, dev, func;
extern void findVideoDevice(void);
extern void help();
extern void usage();
extern char * macMachineType(void);

struct monitor_sync
{
	int h_min;
	int h_max; 
	int v_min;
	int v_max;
};

int main(int argc, char **argv) {
	FILE * f;
	int i, depth, fd;
	int rc = 0, offb = 0, fbdepth = 0, fbdev = 0, nv = 0;
	unsigned int xsstart, xsend, xtotal = 0;
	unsigned int ysstart, ysend, ytotal = 0;
	double drate, vrate, hrate;
	unsigned int htotal, vtotal;
	char manufacturer[4];

	struct monitor_sync sync;
	struct fb_fix_screeninfo fix;
	struct fb_var_screeninfo var;
	struct edid1_info *edid_info = NULL;

	const char * name = "/dev/fb0";
        unsigned char *mem;
	char *macid;
	char *eisaid;

	if (!(fd = open(name, O_RDONLY)))
		rc = 2; /* Failure */

	if (!rc && ioctl(fd, FBIOGET_FSCREENINFO, &fix))
		rc = 2; /* Failure */

	if (!rc && ioctl(fd, FBIOGET_VSCREENINFO, &var))
		rc = 2; /* Failure */

	close(fd);

	/* 640x480 framebuffer typically implies a CRT, so lets bump */
	/* the resolution up to 800x600 to be a bit more sane */
	if (var.xres == 640 && var.yres == 480) {
		var.xres = 800;
		var.yres = 600;
	}

	if (rc) {
		fprintf(stderr, "Can't access %s. Exiting.\n", name);
		return -1;
	}

	if (argc == 2)
	{
		if (!strcmp(argv[1], "--fbdepth"))
			fbdepth = 1;
		else if (!strcmp(argv[1], "--fbdev"))
			fbdev = 1;
		else if (!strcmp(argv[1], "--safe"))
			fbdev = fbdepth = 1;
		else if (!strcmp(argv[1], "--help")) {
			usage();
			exit(0);	
		} else {
			usage();
			exit(0);	
		}
	} else if (argc > 2)  {
		usage();
		exit(0);	

        }

	/* Check to see what type of Mac we're on.*/
	/* e.g. PowerBook1,1  */
	macid = macMachineType();

	sync.h_min = 0;
	sync.h_max = 0;
	sync.v_min = 0;
	sync.v_max = 0;

	/* Check to see if EDID is available */	
	if ( (sizeof(struct edid1_info) == 256) &&
		(sizeof(struct edid_monitor_descriptor) == 18) &&
		get_edid_supported())
	{
		edid_info = get_edid_info();
	
		if ( !(edid_info == NULL) && 
			!(edid_info->version == 0) &&
			!(edid_info->version == 0xff && edid_info->revision == 0xff) &&
	 	    	!(edid_info->version == 0 && edid_info->revision == 0))
		{
		manufacturer[0] = edid_info->manufacturer_name.char1 + 'A' - 1;
		manufacturer[1] = edid_info->manufacturer_name.char2 + 'A' - 1;
		manufacturer[2] = edid_info->manufacturer_name.char3 + 'A' - 1;
		manufacturer[3] = '\0';
		eisaid = malloc(sizeof(manufacturer) + sizeof(edid_info->product_code));	
		sprintf(eisaid, "%s%04x", manufacturer, edid_info->product_code);

		for (i = 0; i < 4; i++)
		{
			struct edid_monitor_descriptor *monitor = NULL;

			monitor = &edid_info->monitor_details.monitor_descriptor[i];

			if (monitor->type == edid_monitor_descriptor_range)
			{
				sync.h_min = monitor->data.range_data.horizontal_min;				
				sync.h_max = monitor->data.range_data.horizontal_max;				
				sync.v_min = monitor->data.range_data.vertical_min;
				sync.v_max = monitor->data.range_data.vertical_max;
			}
		}
		/* On certain Apple LCD panels, set vertical min and max */
		if ((eisaid) && (!strcmp(eisaid, "APP1592") ||
			!strcmp(eisaid, "APPf401") ||
			!strcmp(eisaid, "APP1792") ||
			!strcmp(eisaid, "APP1992") ||
			!strcmp(eisaid, "APP1692") ||
			!strcmp(eisaid, "APP1892")))
			sync.v_min = 50;
			sync.v_max = 60;
		}
	}

	/* More or less taken from Geert Uytterhoeven's fbset (GPL'ed as well..) */
	htotal = var.left_margin+var.xres+var.right_margin+var.hsync_len;
	vtotal = var.upper_margin+var.yres+var.lower_margin+var.vsync_len;
	drate = (1E12/var.pixclock);
	hrate = drate/htotal;
	vrate = hrate/vtotal;
	xsstart = var.xres+var.right_margin;
	xsend = xsstart+var.hsync_len;
	xtotal = xsend+var.left_margin;
	ysstart = var.yres+var.lower_margin;
	ysend = ysstart+var.vsync_len;
	ytotal = ysend+var.upper_margin;
	if (var.bits_per_pixel == 8)
		depth = 8;
	else
		depth = var.red.length+var.green.length+var.blue.length;

	/* look for nvidia and offb */
	if (fix.id)
	{
		mem = strdup(fix.id);
		while(((i = strlen(mem)) > 0) && isspace(mem[i - 1])) {
			mem[i - 1] = '\0';
		}
		if (strstr(fix.id, "NVDA") != NULL)
			nv = 1;

		if (!strncmp(fix.id, "OFfb", 4))
			offb = 1;
	}

	findVideoDevice(); /* For BusID */

	/* Let's be nice and backup an existing xorg.conf */
	if ((f = fopen("/etc/X11/xorg.conf", "r"))) {
	       system("mv /etc/X11/xorg.conf /etc/X11/xorg.xorgautoconfig");
	}

	if (!(f = fopen("/etc/X11/xorg.conf", "w"))) {
		fprintf(stderr, "Can't write to /etc/X11/xorg.conf. Exiting.\n");
		return -1;;
	}


	/*** BEGIN xorg.conf ***/
	fprintf(f, "%s", header);

	/* Set fontpaths */
	DIR *fontdir = opendir(FONTPATH);
	DIR *testdir;
	char path[256];
	struct dirent *fontpath;

	/* Add x fontserver note */
	fprintf(f, "%s", fontpath_xfs);
	if(fontdir != NULL) {
		while ((fontpath = readdir (fontdir))) {
			if((fontpath->d_name)[0] != '.') {
				/* Only add directories */
				strcpy(path, FONTPATH);	
				strcat(path, fontpath->d_name);
				strcat(path, "/");
				testdir = opendir(path);
				if(testdir != NULL) {
					fprintf(f, "\tFontPath\t\"%s\"\n",
						path);
					if(!strncmp(fontpath->d_name,"75dpi",5) ||
					   !strncmp(fontpath->d_name,"100dpi",6)){
						fprintf(f, "\tFontPath\t\"%s:unscaled\"\n",
							path);
					}
					closedir(testdir);
				}
			}
		}
		closedir(fontdir);
	}
	else {
		fprintf(f, "%s", fontpath_gentoo);
	}
	fprintf(f, "EndSection\n\n");

	/* modules section */
	fprintf(f, "%s", modules_section);

	/* extensions section */
	fprintf(f, "%s", extensions_section);

	/* serverflags section */
	fprintf(f, "%s", serverflags_section);

	/* input dev section */
	fprintf(f, "%s", inputdev_section);
	
	/* keyboard */
	fprintf(f, "%s", keyboard_linux);

	/* mouse */
	fprintf(f, "%s", mouse_section);
	fprintf(f, "IMPS/2\"\n");
	fprintf(f, "	Option		\"Device\"	\"/dev/input/mice\"\n");
	fprintf(f, "EndSection\n\n");

	/* monitor section */
	if (!offb)
	    fprintf(f, "%s", monitor_section);
	else
	    fprintf(f, "%s", monitor_section_nomodes);

	/* Use EDID's horizsync / vert refresh first, otherwise try to be sane */
	if (sync.h_min && sync.h_max && sync.v_min && sync.v_max)
	{
		fprintf(f, "\tHorizSync	%d-%d\n\tVertRefresh	%d-%d\n",
			sync.h_min, sync.h_max, sync.v_min, sync.v_max);
	} else if (!strncmp(macid, "PowerMac4,2", 11)) {
		fprintf(f, "\tHorizSync 28-49	\n\tVertRefresh	50-60\n");
	} else if (sync.v_min && sync.v_max) {
		/* Apple Studio/Cinema Display case */
		fprintf(f, "\tHorizSync	30-130\n\tVertRefresh	%d-%d\n",
			sync.v_min, sync.v_max);
	} else {
		if(var.xres <= 640)
			fprintf(f, "	HorizSync	28-33\n	VertRefresh	43-72\n");
		else if(var.xres <= 800)
			fprintf(f, "	HorizSync	28-50\n	VertRefresh	43-75\n");
		else if(var.xres <= 1024)
			fprintf(f, "	HorizSync	30-70\n	VertRefresh	50-160\n");
		else if(var.xres <= 1152)
			fprintf(f, "	HorizSync	30-100\n	VertRefresh	50-160\n");
		else
			fprintf(f, "	HorizSync	30-130\n	VertRefresh	50-160\n");
	}

	if (!offb)
	{
	fprintf(f, "%s", modes_section);
	    fprintf(f, "	# D: %5.3f MHz, H: %5.3f kHz, V: %5.3f Hz\n", drate/1E6,
		       	    hrate/1E3, vrate);
	    fprintf(f, "	Modeline \"%dx%d\" %5.3f %d %d %d %d  %d %d %d %d ",
			    var.xres, var.yres, drate/1E6, var.xres, xsstart, xsend,
			    xtotal, var.yres, ysstart, ysend, ytotal);
	    if (var.vmode & FB_VMODE_INTERLACED)
		    fprintf(f, " Interlace");
	    if (var.vmode & FB_VMODE_DOUBLE)
		    fprintf(f, " DoubleScan");
	    if (var.sync & FB_SYNC_HOR_HIGH_ACT)
		    fprintf(f, " +HSync");
	    else
		    fprintf(f, " -HSync");
	    if (var.sync & FB_SYNC_VERT_HIGH_ACT)
		    fprintf(f, " +VSync");
	    else
		    fprintf(f, " -VSync");
	    if (var.sync & FB_SYNC_COMP_HIGH_ACT)
		    fprintf(f, " Composite");
	    if (var.sync & FB_SYNC_BROADCAST)
		    fprintf(f, " bcast");
        }
	fprintf(f, "%s", device_start);
    depth = 16;

	/* See if we know where the card is, if not... */
	if (nv) {
		if (!strncmp(macid, "PowerMac4,2", 11) || !strncmp(macid, "PowerMac4,5", 11))
			fprintf(f, "	Option	\"FlatPanel\"\n");
		fprintf(f, "%s", driver_nv);
		fprintf(f, "	Driver\t\t\"nv\"\n");
	} else if((fbdev) || (bus == 0 && dev == 0 && func == 0))  {
		if (!fbdev) {
			fprintf(f, "	# We couldn't determine the BusID of your ");
			fprintf(f, "video card.  So we will use\n# the fbdev driver\n");
		}
		fprintf(f, "	#Option	\"ShadowFB\"	\"true\"\n");
		fprintf(f, "	#Option	\"fbdev\"	\"%s\"\n", name);
		fprintf(f, "	Driver\t\t\"fbdev\"\n");
		fprintf(f, "	#BusID	\"0:0:0\"\n");
        depth = 15;
	} else {
		/* Try and guess what video card is behind /dev/fb0 */
		switch(fix.accel) {
			/* ATI Mach64 Family cards */
			case 6:
			case 8:
			case 9:
			case 10:
				fprintf(f, "%s", driver_on_opts);
				fprintf(f, "	Driver\t\t\"ati\"\n");
				break;
			/* IMS Twin Turbo */
			case 14:
				fprintf(f, "%s", driver_off_opts);
				fprintf(f, "	Driver\t\t\"imstt\"\n");
				break;
			/* Matrox Family */
			case 16:
			case 17:
			case 18:
			case 20:
			case 21:
			case 26:
				fprintf(f, "%s", driver_off_opts);
				fprintf(f, "	Driver\t\t\"mga\"\n");
				break;
			/* Chips&Technology 6555x */
			/* Apparently this is broken...
			case 30:
				fprintf(f, "%s", driver_off_opts);
				fprintf(f, "	Driver\t\t\"chips\"\n");
				break;
			*/
			/* VooDoo 3 / Banshee */
			case 31:
				fprintf(f, "%s", driver_off_opts);
				fprintf(f, "	Driver\t\t\"tdfx\"\n");
				break;
			/* ATI Rage 128 Family */
			case 32:
				fprintf(f, "%s", driver_r128);
				fprintf(f, "	Driver\t\t\"r128\"\n");
				break;
			/* ATI Radeon */
			case 38:
				fprintf(f, "%s", driver_radeon);
				fprintf(f, "	Driver\t\t\"radeon\"\n");
				break;
			default:
				/* FIXME still broken? */
				/* radeon seems to be broken on VE (aka 7000) */
				if ((!strncmp(fix.id, "ATI Radeon", 10)) &&
				   (strncmp(fix.id, "ATI Radeon VE", 13))) { 
					fprintf(f, "%s", driver_radeon);
					fprintf(f, "	Driver\t\t\"radeon\"\n");
				} else if (!strncmp(fix.id, "Rage128", 7)) {
					fprintf(f, "%s", driver_r128);
					fprintf(f, "	Driver\t\t\"r128\"\n");
				} else if (!strncmp(fix.id, "Rage Mobility M3", 16)) {
					fprintf(f, "%s", driver_r128);
					fprintf(f, "	Driver\t\t\"r128\"\n");
				} else {
					fprintf(f, "	#Option	\"ShadowFB\"");
					fprintf(f, "	\"true\"\n");
					fprintf(f, "	Driver\t\t\"fbdev\"\n");
					depth = 15;
				}
				break;
		}
		fprintf(f, "	BusID	\"PCI:%d:%d:%d\"\n", bus, dev, func);
	}
	fprintf(f, "%s", screen_section);
	fprintf(f, "%d\n", depth);
	if(fbdepth && depth != var.bits_per_pixel)
		fprintf(f,"	DefaultFbBpp	%d\n", var.bits_per_pixel);

	/* Print this out for 8 / 16 / 24 */
	for (i = 8;i <= 24;i = i + 8)
	{
		fprintf(f, "	SubSection \"Display\"\n");
		fprintf(f, "		Depth		%d\n", i);

		/* If they've got 32bit use it, otherwise use 24 */
		if (depth == 24 && depth != var.bits_per_pixel)
			fprintf(f, "		FbBpp		%d\n",
					var.bits_per_pixel);

		fprintf(f, "		Modes 		\"%dx%d\"\n", var.xres,
				var.yres);
		fprintf(f, "	EndSubSection\n");
	}

	/* To catch depth = 15, common :( */
	if(depth == 15) {
		fprintf(f, "	SubSection \"Display\"\n");
		fprintf(f, "		Depth		%d\n", depth);

		fprintf(f, "		FbBpp		%d\n", 
			var.bits_per_pixel);

		fprintf(f, "		Modes		\"%dx%d\"\n", var.xres,
			       	var.yres);
		fprintf(f, "	EndSubSection\n");
	}

	fprintf(f, "EndSection\n");
	fprintf(f, "%s", dri_section);
	/*** END Xorg.conf ***/

    	fclose(f);

	fprintf(stdout, "Wrote /etc/X11/xorg.conf\n");
	fprintf(stdout, "Please check the configuration file for additional options\n");

	return 0;
}
