/*
 * xf86config; menuing X configuration program, built on the bones of
 * the grotty old text mode xf86config.
 *
 * Copyright (c) 1997-2001 David L Parsons 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by David L Parsons (orc@pell.chi.il.us).  My name may not be used
 * to endorse or promote products derived from this software without
 * specific prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 *
 * I do not know which copyright came with the original program; I pulled
 * it from the XFree86 3.3.1 distribution, and it comes with RCS tags
 * identifying it as a X Consortium program.  I _PRESUME_ it's shipped
 * under the X copyright, but here is the comment header for the
 * original program:
 */

/* $XFree86: xc/programs/server/hw/xfree86/xf86config/xf86config.c,v 3.37.2.4 1997/07/19 04:59:37 dawes Exp $ */

/*
 * This is a configuration program that will create a base XF86Config
 * file based on menu choices. Its main feature is that clueless users
 * may be less inclined to select crazy sync rates way over monitor spec,
 * by presenting a menu with standard monitor types. Also some people
 * don't read docs unless an executable that they can run tells them to.
 */
/* $XConsortium: xf86config.c /main/21 1996/10/28 05:43:57 kaleb $ */

#include "xf86config.h"
#include "util.h"
#include <assert.h>

#ifndef STAGE2
# define STAGE2 0
#endif

int tested;

/* things we want to configure in Jonestown
 */
static Mouse mouse;
static KeyBoard keyboard;
static Display display;
static Geometry geometry;
static VideoCard video;

/* handy defaults for autodetection and test xservers
 */
Mouse generic_mouse;
KeyBoard generic_kb;
Display generic_display;
Geometry generic_geometry;

/* standard modes */
static ModeLine std_rez[35];
static int NR_std = sizeof std_rez/sizeof std_rez[0];

static void
init_std()
{
   std_rez[0].is(640, 400, 25.17, "640  664  760  800   400  409  411  450");
   std_rez[1].is(640, 400, 31.50, "640  672 736   832   400  401  404  445 -HSync +VSync");
   std_rez[2].is(640, 400, 36.00, "640  696  752  832   480  481  484  509 -HSync -VSync");
   std_rez[3].is(640, 480, 25.17, "640  664  760  800   480  491  493  525");
   std_rez[4].is(640, 480, 31.50, "640  680  720  864   480  488  491  521");
   std_rez[5].is(640, 480, 45.80, "640  672  768  864   480  488  494  530 -HSync -VSync");
   std_rez[6].is(640, 480, 31.50, "640  656  720  840   480  481  484  500 -HSync -VSync");
   std_rez[7].is(800, 600, 36.00, "800  824  896 1024   600  601  603  625");
   std_rez[8].is(800, 600, 40.00, "800  840  968 1056   600  601  605  628 +hsync +vsync");
   std_rez[9].is(800, 600, 50.00, "800  856  976 1040   600  637  643  666 +hsync +vsync");
   std_rez[10].is(800, 600, 60.75, "800  864  928 1088   600  616  621  657 -HSync -VSync");
   std_rez[11].is(800, 600, 69.65, "800  864  928 1088   600  604  610  640 -HSync -VSync");
   std_rez[12].is(1024, 768, 44.90, "1024 1048 1208 1264   768  776  784  817 Interlace");
   std_rez[13].is(1024, 768, 65.00, "1024 1032 1176 1344   768  771  777  806 -hsync -vsync");
   std_rez[14].is(1024, 768, 75.00, "1024 1048 1184 1328   768  771  777  806 -hsync -vsync");
   std_rez[15].is(1024, 768, 85.00, "1024 1032 1152 1360   768  784  787  823");
   std_rez[16].is(1024, 768, 115.50, "1024 1056 1248 1440  768  771  781  802 -HSync -VSync");
   std_rez[17].is(1024, 768, 98.90, "1024 1056 1216 1408   768 782 788 822 -HSync -VSync");
   std_rez[18].is(1152, 864, 65.00, "1152 1168 1384 1480   864  865  875  985 Interlace");
   std_rez[19].is(1152, 864, 110.00, "1152 1240 1324 1552   864  864  876  908");
   std_rez[20].is(1152, 864, 135.00, "1152 1464 1592 1776   864  864  876  908");
   std_rez[21].is(1152, 864, 137.65, "1152 1184 1312 1536   864  866  885  902 -HSync -VSync");
   std_rez[22].is(1152, 864, 89.90, "1152 1216 1472 1680   864  868  876  892 -HSync -VSync");
   std_rez[23].is(1152, 864, 92.00, "1152 1208 1368 1474   864  865  875  895");
   std_rez[24].is(1280, 1024, 80.00, "1280 1296 1512 1568  1024 1025 1037 1165 Interlace");
   std_rez[25].is(1280, 1024, 110.00, "1280 1328 1512 1712  1024 1025 1028 1054");
   std_rez[26].is(1280, 1024, 126.50, "1280 1312 1472 1696  1024 1032 1040 1068 -HSync -VSync");
   std_rez[27].is(1280, 1024, 135.00, "1280 1312 1416 1664  1024 1027 1030 1064");
   std_rez[28].is(1280, 1024, 135.00, "1280 1312 1456 1712  1024 1027 1030 1064");
   std_rez[29].is(1280, 1024, 157.50, "1280 1344 1504 1728  1024 1025 1028 1072 +HSync +VSync");
   std_rez[30].is(1280, 1024, 181.75, "1280 1312 1440 1696  1024 1031 1046 1072 -HSync -VSync");
   std_rez[31].is(1600, 1200, 162.00, "1600 1664 1856 2160  1200 1201 1204 1250 +HSync +VSync");
   std_rez[32].is(1600, 1200, 189.00, "1600 1664 1856 2160  1200 1201 1204 1250 -HSync -VSync");
   std_rez[33].is(1600, 1200, 202.50, "1600 1664 1856 2160  1200 1201 1204 1250 +HSync +VSync");
   std_rez[34].is(1600, 1200, 220.00, "1600 1616 1808 2080  1200 1204 1207 1244 +HSync +VSync");
}

static void
init_globals()
{
    int x;

    init_std();

    generic_mouse.is(SYSMOUSE, 0, 0, 0, "/dev/gpmdata");
#if STAGE2
    mouse.is(SYSMOUSE, 0, 0, 0, "/dev/gpmdata");
#endif
    generic_kb.is(1, 1, 0);
    generic_geometry.is(8,32000);

    Monitor::parse_database(Error);
    for (x=0; x < nr_monitor; x++)
	if (streq(monitor[x].vendor(), "VESA"))
	    break;
    generic_display.is(x < nr_monitor ? x : 0);
}

/*
 * This is the initial intro text that appears when the program is started.
 */

const unsigned char intro_text[] =
#if STAGE2
"This dialog will set up your local X configuration and write it to\n"
"the file /etc/" CONFIGNAME ".  For most display adaptors and video\n"
"cards this should be sufficient to properly configure your windowing\n"
"system, but some esoteric cards may require manual editing of the\n"
"X configuration after the system is completely installed." ;
#else
"This program will create a basic " CONFIGNAME " file, based on menu\n"
"selections you make.\n"
"\n"
"The " CONFIGNAME " file usually resides in " TREEROOTLX " or /etc. A\n"
"sample " CONFIGNAME " file is supplied with XFree86; it is configured for a\n"
"standard VGA card and monitor with 640x480 resolution.\n"
"\n"
"You can either take the sample " CONFIGNAME " as a base and edit it for\n"
"your configuration, or let this program produce a base " CONFIGNAME "\n"
"file for your configuration and fine-tune it. Refer to " TREEROOTLX "\n"
"/doc/README.Config for a detailed overview of the configuration\n"
"process.\n"
"\n"
"For accelerated servers (including accelerated drivers in the SVGA\n"
"server), there are many chipset and card-specific options and settings.\n"
"This program does not know about these. On some configurations some of\n"
"these settings must be specified. Refer to the server man pages and\n"
"chipset-specific READMEs.\n" ;
#endif

const unsigned char finalcomment_text[] =
"Within the server press ctrl, alt and '+' simultaneously to cycle video\n"
"resolutions. Pressing ctrl, alt and backspace simultaneously immediately\n"
"exits the server (use if the monitor doesn't sync for a particular\n"
"mode).  For further configuration, refer to " TREEROOTLX "\n"
"/doc/README.Config." ;

/*
 * Xmesg() puts up a messagebox with an [OK] button.
 */
int
Xmesg(char *title, char *prompt)
{
    void* chain;
    int rc;

    chain = ObjChain(0, newOKButton(1, "OK", 0, 0));

    rc = MENU(chain, -1, -1, title, prompt, 0);

    deleteObjChain(chain);

    return (rc == MENU_OK) ? 0 : 1;

} /* Xmesg */


static int
Xmenu_abandonship(void* obj, void* display)
{
    return -1;
} /* Xmenu_abandonship */


/*
 * Xmenu() puts up a menubox that contains a list and a [BACK] button;  you
 * select an item by clicking on it and you back out by either hitting the
 * [BACK] button or ESCaping out of the menu.
 *
 * returns: EOF if [BACK], ESC, or some error.
 *          >= 0 if an item is selected.
 */
int
Xmenu(char *title, char *prompt, int listdepth, ListItem *list,
      int nrlist, int vis, char *button)
{
    void *chain;
    void *menu;
    int rc;
    int x;

    menu = newMenu(-1,0,-1,(nrlist<listdepth)?nrlist:listdepth,
			nrlist,list,0,"",
			vis ? (MENU_SELECTION|SHOW_IDS) : MENU_SELECTION,
			Xmenu_abandonship,0);
    chain = ObjChain(menu, newCancelButton(0, (char*)(button ? button : "BACK"), 0, 0));

    for (x=0; x < nrlist; x++)
	if (list[x].selected)
	    break;
    if (x < nrlist)
	setObjCursor(menu,x);

    rc = MENU(chain, -1, -1, title, prompt, 0);

    deleteObjChain(chain);

    if (rc == MENU_OK)
	for (x=0; x <nrlist;  x++)
	    if (list[x].selected)
		return x;
    return EOF;
} /* Xmenu */

/*
 * Card database.
 */

static char *cardunsupported_text =
"This card is basically UNSUPPORTED. It may only work as a generic\n"
"VGA-compatible card. If you have an XFree86 version more recent than what\n"
"this card definition was based on, there's a chance that it is now\n"
"supported.\n";


/*
 * CMP() card database entry comparison function. 
 */
inline int
CMP(char *t, char *tab)
{
    register int size = strlen(t);

    return tab && strncasecmp(t,tab,size) == 0 && tab[size] == 0;
} /* CMP */


/*
 * nonspace() skips to the first nonspace character in a string
 */
static char*
nonspace(char *s)
{
    while (isspace(*s))
	++s;
    return s;
} /* nonspace */


/* (hv) to avoid the UNIXISM to try to open a dir to check for existance */
static int
exists_dir(char *name)
{
    struct stat sbuf;

    /* is it there ? */
    if (stat(name, &sbuf) == -1)
	return 0;

    /* is there, but is it a dir? */
    return ((sbuf.st_mode & S_IFMT) == S_IFDIR) ? 1 : 0;
}

void
setXlink()
{
    char msg[1000];
    int rc;

    mkdir("/var", 0644);
    mkdir("/var/X11R6", 0644);
    mkdir("/var/X11R6/bin", 0644);

#if !STAGE2
    sprintf(msg, "Make %sserver XF86_%s\n"
		 "the default X server?",
		 video.accelerated() ? "accelerated ":"",
		 video.server());
    if ((rc = dialog_yesno(TITLE, msg, -1, -1)) != 0)
	return;
#endif

    sprintf(msg, "/usr/X11R6/bin/XF86_%s", video.server());
    unlink("/var/X11R6/bin/X");
    if (symlink(msg, "/var/X11R6/bin/X") != 0)
	Error(msg);
} /* setXlink */


void
write_configuration()
{
    char *p;
    int rc;

    if (Incomplete("write your X Configuration"))
	return;
    if (!tested) {
	rc = dialog_yesno(TITLE,
	    "You have made changes to your X configuration, but you\n"
	    "have not yet tested them.  Would you like to test this\n"
	    "configuration now?", -1, -1);
	if (rc != 0 || test_configuration() != 1)
	    return;
    }
    if ((p = ask_XF86Config_location()) != 0) {
	write_XF86Config(p, &video, &display, &geometry, &keyboard, &mouse);
	setXlink();
	end_dialog();
	exit(0);
    }
} /* write_configuration */


/*
 * Create the XF86Config file.
 */

static char *XF86Config_firstchunk_text =
"# File generated by "
#ifdef STAGE2
"Xsetup\n"
#else
"Xconfig\n"
#endif
"#\n"
"# Copyright (c) 1995 by The XFree86 Project, Inc.\n"
"# Copyright (c) 1997-2001 by David Parsons (orc@pell.chi.il.us)\n"
"#\n"
"# Permission is hereby granted, free of charge, to any person obtaining a\n"
"# copy of this software and associated documentation files (the \"Software\"),\n"
"# to deal in the Software without restriction, including without limitation\n"
"# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
"# and/or sell copies of the Software, and to permit persons to whom the\n"
"# Software is furnished to do so, subject to the following conditions:\n"
"# \n"
"# The above copyright notice and this permission notice shall be included in\n"
"# all copies or substantial portions of the Software.\n"
"# \n"
"# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
"# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
"# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n"
"# THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
"# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF\n"
"# OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
"# SOFTWARE.\n"
"# \n"
"# Except as contained in this notice, the name of the XFree86 Project shall\n"
"# not be used in advertising or otherwise to promote the sale, use or other\n"
"# dealings in this Software without prior written authorization from the\n"
"# XFree86 Project.\n"
"#\n"
"\n"
"# **********************************************************************\n"
"# Refer to the XF86Config(4/5) man page for details about the format of \n"
"# this file.\n"
"# **********************************************************************\n"
"\n"
"# **********************************************************************\n"
"# Files section.  This allows default font and rgb paths to be set\n"
"# **********************************************************************\n"
"\n"
"Section \"Files\"\n"
"\n"
"# The location of the RGB database.  Note, this is the name of the\n"
"# file minus the extension (like \".txt\" or \".db\").  There is normally\n"
"# no need to change the default.\n"
"\n"
"    RgbPath	\"" TREEROOTLX "/rgb\"\n"
"\n"
"# Multiple FontPath entries are allowed (which are concatenated together),\n"
"# as well as specifying multiple comma-separated entries in one FontPath\n"
"# command (or a combination of both methods)\n"
"# \n"
"# If you don't have a floating point coprocessor and emacs, Mosaic or other\n"
"# programs take long to start up, try moving the Type1 and Speedo directory\n"
"# to the end of this list (or comment them out).\n"
"# \n"
"\n";

static char *XF86Config_fontpaths[] =
{
    "/fonts/misc/",
    "/fonts/75dpi/:unscaled",
    "/fonts/100dpi/:unscaled",
    "/fonts/Type1/",
    "/fonts/Speedo/",
    "/fonts/75dpi/",
    "/fonts/100dpi/",
    0				/* end of fontpaths */
};

static char *XF86Config_fontpathchunk_text =

"\n"
"# For OSs that support Dynamically loaded modules, ModulePath can be\n"
"# used to set a search path for the modules.  This is currently supported\n"
"# for Linux ELF, FreeBSD 2.x and NetBSD 1.x.  The default path is shown\n"
"# here.\n"
"\n"
"#    ModulePath \"" MODULEPATH "\"\n"
"\n"
"EndSection\n"
"\n"
"# **********************************************************************\n"
"# Server flags section.\n"
"# **********************************************************************\n"
"\n"
"Section \"ServerFlags\"\n"
"\n"
"# Uncomment this to cause a core dump at the spot where a signal is \n"
"# received.  This may leave the console in an unusable state, but may\n"
"# provide a better stack trace in the core dump to aid in debugging\n"
"\n"
"#    NoTrapSignals\n"
"\n"
"# Uncomment this to disable the <Crtl><Alt><BS> server abort sequence\n"
"# This allows clients to receive this key event.\n"
"\n"
"#    DontZap\n"
"\n"
"# Uncomment this to disable the <Crtl><Alt><KP_+>/<KP_-> mode switching\n"
"# sequences.  This allows clients to receive these key events.\n"
"\n"
"#    DontZoom\n"
"\n"
"# Uncomment this to disable tuning with the xvidtune client. With\n"
"# it the client can still run and fetch card and monitor attributes,\n"
"# but it will not be allowed to change them. If it tries it will\n"
"# receive a protocol error.\n"
"\n"
"#       DisableVidModeExtension\n"
"\n"
"# Uncomment this to enable the use of a non-local xvidtune client. \n"
"\n"
"#       AllowNonLocalXvidtune\n"
"\n"
"# Uncomment this to disable dynamically modifying the input device\n"
"# (mouse and keyboard) settings. \n"
"\n"
"#       DisableModInDev\n"
"\n"
"# Uncomment this to enable the use of a non-local client to\n"
"# change the keyboard or mouse settings (currently only xset).\n"
"\n"
"#       AllowNonLocalModInDev\n"
"\n"
"EndSection\n"
"\n";

char *keyboardchunk1_text =
"# **********************************************************************\n"
"# Keyboard section\n"
"# **********************************************************************\n"
"\n"
"Section \"Keyboard\"\n"
"\n"
"    Protocol	\"Standard\"\n"
"\n"
"# when using XQUEUE, comment out the above line, and uncomment the\n"
"# following line\n"
"\n"
"#    Protocol	\"Xqueue\"\n"
"\n"
"    AutoRepeat	500 5\n"
"# Let the server do the NumLock processing.  This should only be required\n"
"# when using pre-R6 clients\n"
"#    ServerNumLock\n"
"\n"
"# Specify which keyboard LEDs can be user-controlled (eg, with xset(1))\n"
"#    Xleds      1 2 3\n"
"\n"
"# To set the LeftAlt to Meta, RightAlt key to ModeShift, \n"
"# RightCtl key to Compose, and ScrollLock key to ModeLock:\n"
"\n";

static char *keyboardchunk2_text =
"#    RightCtl    Compose\n"
"#    ScrollLock  ModeLock\n"
"\n"
"# To disable the XKEYBOARD extension, uncomment XkbDisable.\n"
"\n";

static char *keyboardchunk3_text =
"# To customise the XKB settings to suit your keyboard, modify the\n"
"# lines below (which are the defaults).  For example, for a non-U.S.\n"
"# keyboard, you will probably want to use:\n"
"#    XkbModel    \"pc102\"\n"
"# If you have a US Microsoft Natural keyboard, you can use:\n"
"#    XkbModel    \"microsoft\"\n"
"#\n"
"# Then to change the language, change the Layout setting.\n"
"# For example, a german layout can be obtained with:\n"
"#    XkbLayout   \"de\"\n"
"# or:\n"
"#    XkbLayout   \"de\"\n"
"#    XkbVariant  \"nodeadkeys\"\n"
"#\n"
"# If you'd like to switch the positions of your capslock and\n"
"# control keys, use:\n"
"#    XkbOptions  \"ctrl:swapcaps\"\n"
"\n"
"# These are the default XKB settings for XFree86\n"
"#    XkbRules    \"xfree86\"\n"
"#    XkbModel    \"pc101\"\n"
"#    XkbLayout   \"us\"\n"
"#    XkbVariant  \"\"\n"
"#    XkbOptions  \"\"\n"
"\n";

static char *keyboardchunk4_text =
"\n";

static char *keyboardlastchunk_text =
"\n"
"EndSection\n"
"\n"
"\n";

static char *pointersection_text1 =
"# **********************************************************************\n"
"# Pointer section\n"
"# **********************************************************************\n"
"\n"
"Section \"Pointer\"\n";

static char *pointersection_text2 =
"\n"
"# When using XQUEUE, comment out the above two lines, and uncomment\n"
"# the following line.\n"
"\n"
"#    Protocol	\"Xqueue\"\n"
"\n"
"# Baudrate and SampleRate are only for some Logitech mice\n"
"\n"
"#    BaudRate	9600\n"
"#    SampleRate	150\n"
"\n"
"# Emulate3Buttons is an option for 2-button Microsoft mice\n"
"# Emulate3Timeout is the timeout in milliseconds (default is 50ms)\n"
"\n";

static char *monitorsection_text1 =
"# **********************************************************************\n"
"# Monitor section\n"
"# **********************************************************************\n"
"\n"
"# Any number of monitor sections may be present\n"
"\n"
"Section \"Monitor\"\n"
"\n";

static char *devicesection_text =
"# **********************************************************************\n"
"# Graphics device section\n"
"# **********************************************************************\n"
"\n"
"# Any number of graphics device sections may be present\n"
"\n"
"# Device configured by "
#ifdef STAGE2
"Xsetup"
#else
"Xconfig"
#endif
":\n"
"\n";

static char *screensection_text1 =
"# **********************************************************************\n"
"# Screen sections\n"
"# **********************************************************************\n"
"\n";

void
write_fontpath_section(FILE * f)
{
    int i;
    char cur[256 + 20], *colon, *hash;

    for (i = 0; XF86Config_fontpaths[i]; i++) {
	strcpy(cur, TREEROOTLX);
	strcat(cur, XF86Config_fontpaths[i]);
	/* remove a ':' */
	colon = strchr(cur + 2, ':');	/* OS/2: C:/...:scaled */
	if (colon)
	    *colon = 0;
	hash = (char*)(exists_dir(cur) ? "" : "#");
	fprintf(f, "%s    FontPath   \"%s%s\"\n",
		hash,
		TREEROOTLX,
		XF86Config_fontpaths[i]);
    }
}


int
Display::print(FILE *f)
{
    if (configured()) {
	fputs(monitorsection_text1, f);
	entry().print(f);
	if (entry().nrmodes() == 0 || ! entry().autoconfigured() )
	    for (int x=0; x < NR_std; x++)
		if (entry().good(std_rez[x]))
		    std_rez[x].print(f);
	fprintf(f, "EndSection\n\n\n");
    }
    return 0;
}

int
KeyBoard::print(FILE *f)
{
    /*
     * Write keyboard section.
     */
    fputs(keyboardchunk1_text, f);

    if (altmeta) {
	fprintf(f, "    LeftAlt     Meta\n");
	fprintf(f, "    RightAlt    ModeShift\n");
    } else {
	fprintf(f, "#    LeftAlt     Meta\n");
	fprintf(f, "#    RightAlt    ModeShift\n");
    }
    fputs(keyboardchunk2_text, f);

    if (disable) {
	fprintf(f, "    XkbDisable\n\n");
    } else {
	fprintf(f, "#    XkbDisable\n\n");

	fputs(keyboardchunk3_text, f);

	if (keymap) {
	    fprintf(f, "    XkbKeymap   \"%s\"\n", keymap);
	    fputs(keyboardchunk4_text, f);
	} else {
	    fprintf(f, "    Xkbkeycodes \"%s\"\n", keycodes);
	    fprintf(f, "    XkbTypes    \"%s\"\n", types);
	    fprintf(f, "    XkbCompat   \"%s\"\n", compat);
	    fprintf(f, "    XkbSymbols  \"%s\"\n", symbols);
	    fprintf(f, "    XkbGeometry \"%s\"\n", geometry);
	}
    }
    fputs(keyboardlastchunk_text, f);
    return 0;
}

int
VideoCard::print(FILE *f)
{
    Card *c;
    char *rd;
    char *cc;

    if (!configured()) return 0;

    c = &entry();

    fputs(devicesection_text, f);
    fprintf(f, "Section \"Device\"\n");
    fprintf(f, "    Identifier  \"%s\"\n", identifier());
    fprintf(f, "    VendorName  \"%s\"\n", c->vendor());
    fprintf(f, "    BoardName   \"%s\"\n", c->product());
    if (memory > 0) {
	/* Rely on server to detect video memory. */
	fprintf(f, "    #VideoRam    %d\n", memory);
    }

    if (c->flags & NEED_CHIPSET) {
	fprintf(f, "    ChipSet     \"%s\"\n", c->chipset());

	/* Add comment lines from card definition. */
	fputs(c->lines, f);
    }

    rd = ramdac ? ramdac : c->ramdac;

    if (rd && !strieq(rd, "generic"))
	fprintf(f, "    Ramdac      \"%s\"\n", rd);
    if (c->dacspeed)
	fprintf(f, "    Dacspeed    %s\n", c->dacspeed);
    cc = clockchip ? clockchip : c->clockchip;
    if (cc && !strieq(cc, "generic"))
	fprintf(f, "    Clockchip   \"%s\"\n", cc);
    else if (numberofclocks == 0)
	fprintf(f, "    # Insert Clocks lines here if appropriate\n");
    else {
	for (int i = 0; i < numberofclocks; i++)
	    fprintf(f, "    Clocks %s\n", clocks[i]);
    }
    fprintf(f, "EndSection\n\n\n");
}

int
Mouse::print(FILE *f)
{
    if (configured() ) {
	fprintf(f, "%s", pointersection_text1);
	fprintf(f, "    Protocol    \"%s\"\n", protocol());
	fprintf(f, "    Device      \"%s\"\n", device);
	fprintf(f, "%s", pointersection_text2);
	fputc(emulate3buttons ? ' ' : '#', f);
	fprintf(f, "   Emulate3Buttons\n");
	fputc(emulate3buttons ? ' ' : '#', f);
	fprintf(f, "   Emulate3Timeout    50\n\n");
	fprintf(f, "# ChordMiddle is an option for some 3-button\n");
	fprintf(f, "# Logitech mice\n\n");
	if (!chordmiddle)
	    fprintf(f, "#");
	fprintf(f, "    ChordMiddle\n\n");
	if (cleardtrrts) {
	    fprintf(f, "    ClearDTR\n");
	    fprintf(f, "    ClearRTS\n\n");
	}
	fprintf(f, "EndSection\n\n\n");
    }
}

void
write_XF86Config(char *filename,
                 VideoCard *video,
		 Display *display,
		 Geometry *geometry,
		 KeyBoard *keyboard,
		 Mouse *mouse)
{
    FILE *f;
    int x;

    /*
     * Write the file.
     */

    if ((f = fopen(filename, "w")) == NULL) {
	Error("Could not write XF86Config: %s", strerror(errno));
	return;
    }
    fputs(XF86Config_firstchunk_text, f);
    write_fontpath_section(f);
    fputs(XF86Config_fontpathchunk_text, f);

    keyboard->print(f);
    mouse->print(f);
    display->print(f);
    video->print(f);

    /*
     * Write Screen sections.
     */
    fputs(screensection_text1, f);

    /*
     * SVGA screen section.
     */
    if (video->servertype == SVGA) {
	fprintf(f,
		"# The Colo(u)r SVGA server\n"
		"\n"
		"Section \"Screen\"\n"
		"    Driver      \"svga\"\n"
		"    #Device      \"Generic VGA\"\n"
		"    Device      \"%s\"\n"
		"    Monitor     \"%s %s\"\n"
		"    Subsection \"Display\"\n"
		"        Depth       8\n"
		"        # Omit the Modes line for the \"Generic VGA\" device\n"
		"        Modes       %s\n"
		"        ViewPort    0 0\n"
		"        # Use Virtual 320 200 for Generic VGA\n",
			video->identifier(),
			display->entry().vendor(), display->entry().product(),
			geometry->bpp8.modes());
	if (geometry->isvirtual)
	    fprintf(f, "        Virtual     %d %d\n",
		    geometry->bpp8.virtualX, geometry->bpp8.virtualY);
	fprintf(f,
		"    EndSubsection\n"
		"    Subsection \"Display\"\n"
		"        Depth       16\n"
		"        Modes       %s\n"
		"        ViewPort    0 0\n",
		geometry->bpp16.modes());
	if (geometry->isvirtual)
	    fprintf(f, "        Virtual     %d %d\n",
		    geometry->bpp16.virtualX, geometry->bpp16.virtualY);
	fprintf(f,
		"    EndSubsection\n"
		"    Subsection \"Display\"\n"
		"        Depth       24\n"
		"        Modes       %s\n"
		"        ViewPort    0 0\n",
		geometry->bpp24.modes());
	if (geometry->isvirtual)
	    fprintf(f, "        Virtual     %d %d\n",
		    geometry->bpp24.virtualX, geometry->bpp24.virtualY);
	fprintf(f,
		"    EndSubsection\n"
		"    Subsection \"Display\"\n"
		"        Depth       32\n"
		"        Modes       %s\n"
		"        ViewPort    0 0\n",
		geometry->bpp32.modes());
	if (geometry->isvirtual)
	    fprintf(f, "        Virtual     %d %d\n",
		    geometry->bpp32.virtualX, geometry->bpp32.virtualY);
	fprintf(f,
		"    EndSubsection\n");
	fprintf(f,
		"    DefaultColorDepth %d\n", geometry->bpp_default );
	fprintf(f,
		"EndSection\n"
		"\n");
    }
    else {
	fprintf(f,
		"# The Colo(u)r SVGA server\n"
		"\n"
		"Section \"Screen\"\n"
		"    Driver      \"svga\"\n"
		"    Device      \"%s\"\n"
		"    Monitor     \"%s %s\"\n"
		"    Subsection \"Display\"\n"
		"        Depth       8\n"
		"        Modes       %s\n"
		"        ViewPort    0 0\n",
		    video->identifier(),
		    display->entry().vendor(),
		    display->entry().product(),
		    geometry->bpp8.modes());
	if (geometry->isvirtual) {
	    fprintf(f, "        Virtual     %d %d\n",
		    geometry->bpp8.virtualX, geometry->bpp8.virtualY);
	}
	fprintf(f, 
		"    EndSubsection\n");
	fprintf(f,
		"    DefaultColorDepth %d\n", geometry->bpp_default );
	fprintf(f,
		"EndSection\n"
		"\n");
    }

    /*
     * VGA16 screen section.
     */
    fprintf(f,
	    "# The 16-color VGA server\n"
	    "\n"
	    "Section \"Screen\"\n"
	    "    Driver      \"vga16\"\n"
	    "    Device      \"%s\"\n"
	    "    Monitor     \"%s %s\"\n"
	    "    Subsection \"Display\"\n"
    /*
     * Depend on 800x600 to be deleted if not available due to
     * dot clock or monitor constraints.
     */
	    "        Modes       \"800x600\" \"640x480\"\n"
	    "        ViewPort    0 0\n"
	    "        Virtual     800 600\n"
	    "    EndSubsection\n"
	    "EndSection\n"
	    "\n",
		video->identifier(),
		display->entry().vendor(),
		display->entry().product());

    /*
     * MONO screen section.
     * This is almost identical to the VGA16 section.
     */
    fprintf(f,
	    "# The Mono server\n"
	    "\n"
	    "Section \"Screen\"\n"
	    "    Driver      \"vga2\"\n"
	    "    Device      \"%s\"\n"
	    "    Monitor     \"%s %s\"\n"
	    "    Subsection \"Display\"\n"
    /*
     * Depend on 800x600 to be deleted if not available due to
     * dot clock or monitor constraints.
     */
	    "        Modes       \"800x600\" \"640x480\"\n"
	    "        ViewPort    0 0\n"
	    "        Virtual     800 600\n"
	    "    EndSubsection\n"
	    "EndSection\n"
	    "\n",
		video->identifier(),
		display->entry().vendor(),
		display->entry().product());

    /*
     * The Accel section.
     */
    fprintf(f,
	    "# The accelerated servers\n"
	    "\n"
	    "Section \"Screen\"\n"
	    "    Driver      \"accel\"\n"
	    "    Device      \"%s\"\n"
	    "    Monitor     \"%s %s\"\n"
	    "    Subsection \"Display\"\n"
	    "        Depth       8\n"
	    "        Modes       %s\n"
	    "        ViewPort    0 0\n",
	    video->identifier(),
	    display->entry().vendor(),
	    display->entry().product(),
	    geometry->bpp8.modes());
    if (geometry->isvirtual)
	fprintf(f, "        Virtual     %d %d\n",
		geometry->bpp8.virtualX, geometry->bpp8.virtualY);
    fprintf(f,
	    "    EndSubsection\n"
	    "    Subsection \"Display\"\n"
	    "        Depth       16\n"
	    "        Modes       %s\n"
	    "        ViewPort    0 0\n",
	    geometry->bpp16.modes());
    if (geometry->isvirtual)
	fprintf(f, "        Virtual     %d %d\n",
		geometry->bpp16.virtualX, geometry->bpp16.virtualY);
    fprintf(f,
	    "    EndSubsection\n"
	    "    Subsection \"Display\"\n"
	    "        Depth       24\n"
	    "        Modes       %s\n"
	    "        ViewPort    0 0\n",
	    geometry->bpp24.modes());
    if (geometry->isvirtual)
	fprintf(f, "        Virtual     %d %d\n",
		geometry->bpp24.virtualX, geometry->bpp24.virtualY);
    fprintf(f,
	    "    EndSubsection\n"
	    "    Subsection \"Display\"\n"
	    "        Depth       32\n"
	    "        Modes       %s\n"
	    "        ViewPort    0 0\n",
	    geometry->bpp32.modes());
    if (geometry->isvirtual)
	fprintf(f, "        Virtual     %d %d\n",
		geometry->bpp32.virtualX, geometry->bpp32.virtualY);
    fprintf(f,
	    "    EndSubsection\n");
    fprintf(f,
	    "    DefaultColorDepth %d\n", geometry->bpp_default );
    fprintf(f,
	    "EndSection\n"
	    "\n");

    fclose(f);

}


static int
writable(char *foo)
{
    int fd = creat(foo, 0644);

    if (fd < 0)
	return 0;
    close(fd);
    return 1;
}


/*
 * Ask where to write XF86Config to. Returns filename or NULL if we want to
 * back up and try again.
 */
char *
ask_XF86Config_location()
{
#if STAGE2
    return "/etc/XF86Config";
#else
    static char location[80];
    int rc;
    int x;
    ListItem *menup;
    int menusize;

    static ListItem menu[] = {
	{ "ETC", "As the default X configuration (/etc/XF86Config)", },
	{ "X11", "/usr/X11R6/lib/X11/X86Config", },
	{ "HERE","in the current directory as XF86Config", },
	{ "SAY", "I'll tell you where to write it.", },
    };
#define NRWRITE	((sizeof menu / sizeof menu[0]) / 2)

    if (getuid() == 0) {
	menusize = NRWRITE;
	menup = menu;
    }
    else {
	menusize = NRWRITE-1;
	menup = menu+1;
    }

    for (x=0; x< menusize; x++)
	menup[x].selected = 0;

    while (1) {
	rc = Xmenu(TITLE,
			  "Where shall I write the XF86Config file?",
			  8, menup, menusize, 0, 0);

	if (rc != 0)
	    return (char*)0;

	if (strieq(menup[rc].id, "SAY")) {
	    while (dialog_inputbox(TITLE,
		"Where do you want to write this XF86Config file?",
				       -1, 60, location) == 0) {

		if (!writable(location)) {
		    Error("Cannot write to %s", location);
		    continue;
		}
		return location;
	    }
	}
	else if (strieq(menup[rc].id, "HERE")) {
	    if (writable("./XF86Config"))
		return "./XF86Config";
	    Error("Current directory is not writable");
	}
	else if (strieq(menup[rc].id, "X11"))
	    return "/usr/X11R6/lib/X11/XF86Config";
	else
	    return "/etc/XF86Config";
    }
#endif
} /* ask_XF86Config_location */


#if !STAGE2
/*
 * Check if an earlier version of XFree86 is installed; warn about proper
 * search path order in that case.
 */
void
path_check()
{
    int rc;
    int fail = 0;
    char *p;

    if (access("/usr/X386/bin", F_OK) != 0)
	return;

    if ((p = getenv("PATH")) != 0) {
	for (p = strtok(p, ":"); p; p = strtok(NULL, ":")) {
	    if (streq(p, "/usr/X386/bin")) {
		fail = 1;
		break;
	    } else if (streq(p, TREEROOT))
		break;
	}
	if (fail) {
	    rc = Xmesg(TITLE, 
"The directory /usr/X386/bin exists and is in your PATH before\n"
TREEROOT "!  If you have an old copy of XFree86 (prior to 3.1)\n"
"installed in /usr/X386/bin, things will break in mysterious ways.\n"
"\n"
"It is imperative that the directory " TREEROOT " is present in\n"
"your search path, *before* any occurrence of /usr/X386/bin. If you\n"
"have installed X program binaries that are not in the base XFree86\n"
"distribution in /usr/X386/bin, you can keep the directory in your\n"
"path as long as it is after " TREEROOT ".");
	    if (rc != 0) {
		end_dialog();
		exit(0);
	    }
	}
    }
} /* path_check */
#endif


/*
 * dirty()
 */
static int
dirty()
{
    return video.touched() || display.touched() || geometry.touched()
			   || mouse.touched() || keyboard.touched();
}


/*
 * Incomplete() complains if you haven't set your mouse, display, and video
 * yet.
 */
int
Incomplete(char *action)
{
    char msg[2000];
    char *prefix;

    if (!dirty())
	return 0;

    msg[0] = 0;
    prefix = "You need to define your ";
    if (!mouse.configured()) {
	strcat(msg, prefix);
	strcat(msg, "mouse");
	prefix = ", ";
    }
    if ( ! display.configured() ) {
	strcat(msg, prefix);
	strcat(msg, "monitor");
	prefix = ", ";
    }
    if ( ! video.configured() ) {
	strcat(msg, prefix);
	strcat(msg, "video card");
	prefix = ", ";
    }
    if ( !geometry.configured() ) {
	strcat(msg, prefix);
	strcat(msg, "resolution ");
	prefix = ", ";
    }
    if ( !keyboard.configured() ) {
	strcat(msg, prefix);
	strcat(msg, "keyboard");
	prefix = ", ";
    }

    if (msg[0]) {
	strcat(msg, "\nbefore I can ");
	strcat(msg, action);

	Error(msg);
	return 1;
    }
    return 0;
} /* Incomplete */


static void
grabtext(int pipe, char **msg, int *size, int delay)
{
    int sizem;
    char bfr[1024];
    fd_set rdr;
    struct timeval tv;
    struct timeval then, now;

    gettimeofday(&then, 0);
    then.tv_sec += delay;

    while (1) {
	FD_ZERO(&rdr);
	FD_SET(pipe, &rdr);
	if (delay == 0) {
	    tv.tv_sec = tv.tv_usec = 0;
	}
	else {
	    gettimeofday(&now, 0);
	    tv.tv_sec = then.tv_sec - now.tv_sec;
	    tv.tv_usec = (then.tv_usec - now.tv_usec) % 1000000;
	}
	if (select(pipe+1, &rdr, 0, 0, &tv) > 0) {
	    if ( (sizem=read(pipe, bfr, sizeof bfr)) > 0 ) {
		*size += sizem;
		bfr[sizem] = 0;
		*msg = (char*)realloc(*msg, 1+(*size));
		strcat(*msg, bfr);
	    }
	}
	else if (delay == 0 || timercmp(&now, &then, >=))
	    return;
    }
}


/*
 * test_configuration() brings up the X server for 8 seconds so you can see
 * how it works.
 * 
 * TODO: spiffy box display like the one Microsoft uses.
 */
int
test_configuration()
{
    int status;
    int top;
    int rc;
    int whoops;
    int io[2];
    char pgm[200];
    pid_t child;

    if (getuid() != 0) {
	Error("Only root can test the X server!");
	return 1;
    }

    if (Incomplete("test your current settings"))
	return 0;

    rc = dialog_yesno(TITLE,
#ifdef STAGE2
		"To confirm your current settings, I will start your X\n"
		"server for 15 seconds.  If this locks up your computer,\n"
		"don't panic -- just turn it off, then on again and you\n"
		"can attempt to set up X again.\n"
		"\n"
		"Shall I start the X server so you can verify that it works?",
#else
		"To test your current settings, I need to temporarily\n"
		"start the X server.  Shall I do it now (it will only\n"
		"run for 15 seconds.)",
#endif
			-1, -1);
    if (rc != 0)
	return 0;

    sync();
    sprintf(pgm, "/usr/X11R6/bin/XF86_%s", video.server());

    top = 0;

    if (pipe(io) != 0) {
	Error("pipe[] setup error in test_configuration()\n"
	      "(shouldn't happen)");
	return 0;
    }

    if ((child = fork()) == 0) {
	write_XF86Config(TMP_XF86CONFIG,&video,&display,&geometry,&keyboard,&mouse);
	dup2(io[1], 1);
	dup2(io[1], 2);
	execl(pgm, "X", "-allowMouseOpenFail", "-xf86config", TMP_XF86CONFIG, 0);
	exit(0);
    }
    else if (child > 0) {
	/* I AM THE PARENT */
	int x;
	char msg[200];
	char *servermessages = (char*)malloc(1);
	int size = 0;

#define TESTTIME 15

	servermessages[0] = 0;
	whoops = 0;
	for (x=TESTTIME; x>0; --x) {
	    do {
		rc = waitpid(child, &status, WNOHANG);
	    } while (rc != child && rc != 0 && rc != -1);

	    if (rc == child) {
		whoops = 1;
		break;
	    }

	    sprintf(msg, "The X server is running in another window\n"
		     "             (%d second%s left)",
				     x, (x > 1) ? "s" : "");
	    dialog_msgbox(TITLE, msg, -1, -1, 0);
	    grabtext(io[0], &servermessages, &size, 1);
	}

	grabtext(io[0], &servermessages, &size, 0);
	close(io[0]);

	/* wait a while, then kill the X server, after sweeping
	 * up all the input.
	 */
	if (whoops == 0) {
	    kill(child, SIGHUP);
	    kill(child, SIGTERM);
	    dialog_msgbox(TITLE,
			  "Now waiting for the X server to die", -1, -1, 0);
	    while (waitpid(child, &status, 0) != child && errno != ECHILD)
		;
	}

	unlink(TMP_XF86CONFIG);

	if (whoops) {
	    void *obj;
	    void *chain;
	    int rc;

	    obj = newText(0, 0, 70, 14, size, 0, 0, servermessages, 0, 0);
	    setObjCursor(obj, -1);
	    chain = ObjChain(newCancelButton(1, "NO", 0, 0),
	                     newOKButton(2, "YES", 0, 0));
	    chain = ObjChain(chain, obj);

	    rc = MENU(chain, -1, -1, "The test didn't work!", 
      "The X server seems to have died.  This means that this configuration\n"
      "is most likely NOT what you want.  Do you want to use it anyway?",
	      ERROR_FLAG|AT_BUTTON);

	    deleteObjChain(chain);
	}
	else 
	    rc = dialog_yesno(TITLE,
				"Is this the configuration you want?", -1, -1);
	tested = (rc == 0);
	free(servermessages);
    }
    else
	Error("fork: %s", strerror(errno));
    return 1;
} /* test_configuration */


/*
 * Program entry point.
 */
int
main(int argc, char **argv)
{
    int x, rc;
    int want_to_die;

static ListItem mainmenu[] = {
    { "M",	"Configure the monitor", },
    { "C",	"Configure the video card", },
    { "U",      "Set resolution and number of colors" },
#if !STAGE2
    { "R",	"Configure the mouse", },
#endif
    { "K",	"Configure the keyboard", },
    { "T",	"Test current configuration", },
    { "W",	"Write the new X configuration, then exit", },
#if STAGE2
    { "Q",	"Do NOT set up X windows on this machine", },
#endif
};
#define NR_MAIN	((sizeof mainmenu / sizeof mainmenu[0]))

    init_dialog();

#if STAGE2
    /* If a path was passed in, chroot there before you do anything else */
    if (argc > 1) {
	if (chdir(argv[1]) != 0)
	    perror(argv[1]);
	else if (chroot(".") != 0)
	    perror(argv[1]);
    }
#else
    path_check();
#endif
    init_globals();

    Xmesg(TITLE, (char*)intro_text);

    while (1) {
	if (!display.configured())
	    rc = 'M';
	else if (!video.configured())
	    rc = 'C';
	else if (!geometry.configured())
	    rc = 'U';
	else if (!mouse.configured())
	    rc = 'R';
	else if (!keyboard.configured())
	    rc = 'K';
	else if (!tested)
	    rc = 'T';
	else
	    rc = 'W';

	for (x = 0; x < NR_MAIN; x++)
	    mainmenu[x].selected = (mainmenu[x].id[0] == rc);

#if STAGE2
	rc = Xmenu( "Set up your X configuration",
		    0, NR_MAIN, mainmenu, NR_MAIN, 0,
		    "FINISH");
#else
	rc = Xmenu( "X configuration tool",
		    0, NR_MAIN, mainmenu, NR_MAIN, 0, 0);
#endif
	want_to_die = (rc < 0);

	if (want_to_die) {
	    if (tested)
		break;
#if STAGE2
	    Error("Please write your X configuration before exiting this\n"
		  "screen.");
	    continue;
#else
	    if (dialog_yesno(TITLE, "Abandon changes?", -1, -1) == 0)
		break;
#endif
	}

	switch (mainmenu[rc].id[0]) {
	case 'M': display.configure(STAGE2);			break;
	case 'C': video.configure(STAGE2);			break;
	case 'U': geometry.configure(STAGE2, &video, &display);	break;
	case 'R': mouse.configure(STAGE2);			break;
	case 'K': keyboard.configure(STAGE2);			break;
	case 'T': test_configuration();				break;
	case 'W': write_configuration();			break;
#if STAGE2
	case 'Q': if (dialog_yesno(TITLE,
			    "Are you sure you don't want to set up\n"
			    "X windows on this machine?", -1, -1) == 0)
		    end_dialog();
		    system("sed -e '/xdm/s/^/#/' < /etc/inittab > /nit");
		    system("mv -f /nit /etc/inittab");
		    exit(0);
		  break;
#endif
	}
    }
    end_dialog();
    exit(0);
} /* main */
