/*
 * video card and resolution setting
 */

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

/*
 * These are from the selected card definition. Parameters from the
 * definition are offered during the questioning about the video card.
 */
static LIA cardmenu = 0;

/*
 * the server menu, placed here because most everyone wants access to
 * it one way or another
 */
static ListItem Server[] = {
    { "Mono",	"640x480 monochrome VGA display" }, 
    { "VGA16",	"640x480 16-color VGA-compatable display" },
    { "SVGA",	"Standard SVGA server" },
    { "3DLabs",	"3DLabs PERMEDIA cards", },
    { "S3",	"S3 cards (not ViRGE)", },
    { "S3V",	"S3 ViRGE cards", },
    { "Mach8",	"ATI Mach8 cards", },
    { "Mach32",	"ATI Mach32 cards", },
    { "Mach64",	"ATI Mach64 cards", },
    { "8514",	"IBM 8514 cards and workalikes", },
    { "P9000",	"Weitek Power 9000 cards", },
    { "AGX",	"AGX cards", },
    { "W32",	"ET4000/W32 cards", },
    { "I128",	"Imagine 128 cards", },
};
static int NRSERVERS = (sizeof Server/sizeof Server[0]);
#define FIRST_ACCEL 3			/* first accelerated server */

int
VideoCard::accelerated()
{
    return (servertype >= FIRST_ACCEL);
}


char *
VideoCard::server()
{
    return Server[servertype].id;
}

/*
 * 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. 
 */
static inline int
CMP(char *t, char *tab)
{
    register int size = strlen(t);

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


/*
 * select_card() finds a given card id in the cardmenu[] tree, then calls
 * find_server_for_card.
 */
int
VideoCard::select_card(int key)
{
    char id[20];
    int i, j;
    LIA submenu;

    sprintf(id, "%d", key);

    for (i=0; i < LIAcount(cardmenu); i++)
	if ((submenu = LIAlist(cardmenu)[i].id) != 0)
	    for (j=0; j < LIAcount(submenu); j++)
		if (streq(LIAlist(submenu)[j].id, id)) {
		    key0 = i;
		    key1 = j;
		    _key = key;
		    return find_server_for_card(1);
		}
    return 0;
} /* select_card */


int
Card::autodetect(int *memory)
{
#define PROBE1 "/usr/X11R6/bin/scanpci -tC3 2>/dev/null"
#define PROBE2	"/usr/X11R6/bin/SuperProbe </dev/null 2>/dev/null | /usr/bin/sed -n -e '/^First/,/^Second/p'"

/*
 * blacklist pci devices that blow away video when SuperProbe is run.
 */
    static struct _blacklist {
	char *vendor;
	char *card;
    } blacklist[] = {
	{ 0, 0 },
    } ;
#define NR_BLACKLIST	(sizeof blacklist/sizeof blacklist[0])

    FILE *f;
    int  i;
    char line[2048];
    char *p;
    int candidate = EOF;
    static char d_ramdac[80];
    static char d_chipset[80];
    int  d_memory = -1;

    _custom = 0;

    *memory = -1;

    /* first scan for PCI devices and see if we can find any
     * of them in our list of cards
     */
    if ( (f = popen(PROBE1, "r")) != 0) {
	while (fgets(line, sizeof line, f)) {

	    strtok(line, "\n");
	    if ((p = strchr(line, '\t')) != 0)
		++p;
	    else
		continue;

	    for (i=0; i < NR_BLACKLIST; i++) {
		/* 
		 * bail if we find a blacklisted card or vendor
		 */
		if (streq(line, blacklist[i].vendor) || streq(p, blacklist[i].card)) {
		    pclose(f);
		    return 0;
		}
	    }

	    for (i=0; i < nrcards; ++i)
		if (card[i].custom())
		    continue;
		else if (CMP(card[i].chipset(), p)) {
		    candidate = i;
		    break;
		}
	}
	pclose(f);
    }

    /* Then run SuperProbe to try and figure out what the
     * card is.  This also picks up the amount of memory in
     * the card (if detectable), the ramdac, and a bunch of
     * other information that the user might not know. Of
     * course if this information isn't picked up, the user
     * will have to know it anyway, so caviat emptor.
     */
    if ( candidate == EOF && (f = popen(PROBE2, "r")) != 0) {
	while (fgets(line, sizeof line, f)) {

	    for (p=line; isspace(*p); ++p)
		;
	    if (strncasecmp(p, "RAMDAC:", 7) == 0) {
		p += 7;
		while (isspace(*p)) ++p;
		strcpy(d_ramdac, p);
		for (p = d_ramdac; *p && !isspace(*p); ++p)
		    ;
		*p = 0;
	    }
	    else if (strncasecmp(p, "CHIPSET:", 8) == 0) {
		p += 8;
		while (isspace(*p))
		    ++p;
		if (strncasecmp("Tseng ", p, 6) == 0) {
		    /* hack around superprobe returning
		     * chipset information that's not
		     * like the Cards database
		     */
		    p += 6;
		    while (isspace(*p))
			++p;
		}
		else if (strncasecmp("Chips & Tech f", p, 14) == 0) {
		    /* Redo `Chips & Tech Fxxxxx' to
		     * CTxxxxx
		     */
		    p += 12;
		    p[0] = 'c';
		    p[1] = 't';
		}
		strcpy(d_chipset, p);
		for (p=d_chipset; *p && *p != '('; ++p)
		    ;
		while (isspace(p[-1]))
		    --p;
		*p = 0;
	    }
	    else if (strncasecmp(p, "MEMORY:", 7) == 0) {
		p += 7;
		while (isspace(*p)) ++p;
		d_memory = atoi(p);
	    }
	}
	pclose(f);

	if (d_ramdac[0] && d_chipset[0])
	    for (i=0; i < nrcards; i++)
		if (card[i].custom())
		    continue;
		else if (CMP(d_chipset, card[i].chipset())
		      && CMP(d_ramdac, card[i].ramdac)) {
		    candidate = i;
		    break;
		}
	if (candidate == EOF && d_chipset[0])
	    for (i=0; i < nrcards; i++)
		if (card[i].custom())
		    continue;
		else if (CMP(d_chipset, card[i].chipset())) {
		    candidate = i;
		    break;
		}
    }
    if (candidate == EOF)
	return 0;

    *memory = d_memory;
    copy(card[candidate]);
    return 1;
} /* Card::autodetect */


int
VideoCard::configure(int stage2)
{
    static int parsed = 0;
    char msg[4000];
    char id[20];
    int nrindex, len;
    char *p, *q;
    int i, x, rc;
    int found = 0;
    Card detected;

    char s[80];

    _stage2 = stage2;
    if (_stage2 == 0) {
	rc = Xmesg(TITLE,
"Now we must configure video card specific settings. At this point you\n"
"can choose to make a selection out of a database of video card\n"
"definitions.  Because there can be variation in Ramdacs and clock\n"
"generators even between cards of the same model, it is not sensible to\n"
"blindly copy the settings (e.g. a Device section). For this reason,\n"
"after you make a selection, you will still be asked about the components\n"
"of the card, with the settings from the chosen database entry presented\n"
"as a strong hint.\n"
"\n"
"If you can't find your card in the database, there's nothing to worry\n"
"about.  You should only choose a database entry that is exactly the same\n"
"model as your card; choosing one that looks similar is just a bad idea\n"
"(e.g. a GemStone Snail 64 may be as different from a GemStone Snail 64+\n"
"in terms of hardware as can be).");

	if (rc != 0)	/* BACK */
	    return 0;
    }

    /* Pick up the card database, if possible.
     */
    if (!parsed) {
	/* Build a two-level LIA; the firstlevel one holds company names
	 * and the second one (contained in LIA[].id) contains the actual
	 * card definitions.
	 */
	parsed = 1;
	Card::parse_database(Error);

	if (getuid() == 0) {
	    if (stage2) {
		dialog_msgbox(TITLE, "Probing your video hardware.\n"
				     "        Please wait", -1, -1, 0);
		sleep(1);
		rc = 0;
	    }
	    else {
		rc = dialog_yesno(TITLE,
		    "Would you like to have me attempt to automatically detect\n"
		    "your video card?",
			-1, -1);
	    }
	    if (rc == 0) {
		if (detected.autodetect(&memory)) {
		    card = (Card*)realloc(card, (1+nrcards) * sizeof card[0]);
		    memmove(&card[1], &card[0], nrcards * sizeof card[0]);
		    card[0].copy(detected);
		    card[0].vendor("(Autodetected)");
		    found = 1;
		}
	    }
	}
	cardmenu = Imenu<Card>::buildmenu(card,nrcards);
	if (found)
	    card[0].vendor(detected.vendor());
    }

    if (found && select_card(0))		/* ask if they want the */
	return 1;				/* detected card */
    else if (configured() && select_card(key()))/* alternatively, if they */
	return 1;				/* want the last configured */
    return choosevideo();
} /* configure */


/*
 * card_callback() sets all of the appropriate keys when a video card
 * is chosen
 */
static int
card_callback(ndObject obj, ndDisplay display)
{
    int k = EOF;
    VideoCard *v;

    if (v = (VideoCard*)getUserData(obj)) {
	getIndexedMenuSelection(obj, &v->key0, &v->key1);

	if (LIAlist(cardmenu)[v->key0].id)
	    sscanf(LIAlist(LIAlist(cardmenu)[v->key0].id)[v->key1].id,
		    "%d", &k);
	v->key(k);
    }
    return -1;
} /* card_callback */


/*
 * choosevideo() puts up a menu with cards and manufacturers so the user
 * can verify or choose a video card
 */
int
VideoCard::choosevideo()
{
    int rc;
    ndObject menu;
    ndObject chain;

    while (1) {
	menu = newIndexedMenu(0, 1,
			      65, 12, -1,
			      cardmenu,
			      0, card_callback, 0/*help*/),
	setUserData(menu,this);

	setIndexedMenuSelection(menu, key0, key1);

	chain = ObjChain(menu, newCancelButton(0, "BACK", 0, 0));

	rc = MENU(chain, -1, -1, "Configure your video card",
	    "Please choose your video card; if you don't see it here, you can\n"
	    "manually configure it later in this program", 0);

	deleteObjChain(chain);

	if (rc != MENU_OK)
	    return 0;

	if (find_server_for_card(0))
	    return 1;
    } /* CHOOSE A VIDEO CARD FROM DATABASE LOOP */
} /* choosevideo */


/*
 * find_server_for_card picks up the server that _key
 * wants to use, then calls screen_configuration to set up any
 * unresolved parameters.
 */
int
VideoCard::find_server_for_card(int didauto)
{
    ListItem *submenu;
    unsigned int i;

    for (i=0; i<NRSERVERS; i++)
	if (strieq(card[_key].server, Server[i].id)) {
	    servertype = i;
	    break;
	}
    if (i == NRSERVERS) {
	Error("server _key not set (can't happen?)");
	return 0;
    }
    return screen_configuration(didauto);
} /* find_server_for_card */

/*
 * Screen/video card configuration.
 */


static ListItem videomemory[] = {
    { "256",	"256 kilobytes", },
    { "512",	"512 kilobytes", },
    { "1024",	"1 megabyte", },
    { "2048",	"2 megabytes", },
    { "4096",	"4 megabytes", },
    { "8192",	"8 megabytes", },
    { "16384",	"16 megabytes", },
    { "other",	"None of the above", },
} ;
#define NRVIDEOMEM	(sizeof videomemory/sizeof videomemory[0])

static const ListItem modestring[] =
{
#ifdef RIDICULOUSLY_LOW_RESOLUTION
    { "320x200",	(char*)  (320*200), },
    { "320x240",	(char*)  (320*240), },
    { "400x300",	(char*)  (400*300), },
    { "512x384",	(char*)  (512*384), }, 
    { "640x400",	(char*)  (640*400), },
#endif
    { "640x480",	(char*)  (640*480), },
    { "800x600",	(char*)  (800*600), },
    { "1024x768",	(char*) (1024*768), },
    { "1152x864",	(char*) (1152*864), },
    { "1280x1024",	(char*)(1280*1024), },
    { "1600x1200",	(char*)(1600*1200), }, 
    /*{ "1800x1400",	(char*)(1800*1400), },*/
};
#define NR_MODESTRING	(sizeof modestring/sizeof modestring[0])

/*
 * ask questions about the screen
 */
int
VideoCard::screen_configuration(int didauto)
{
    int rc;
    ListItem *submenu;
    char msg[1024];

    assert(_key >= 0);
    sprintf(msg, "Your video card definition:\n"
		 "Identifier: %s\n"
		 "Chipset:    %s\n"
		 "Server:     XF86_%s\n",
		      card[_key].name,
		      card[_key].chipset(),
		      card[_key].server);

    if (memory > 0)
	sprintf(END(msg), "Memory:     %ld Kb\n", memory);
    if (ramdac)
	sprintf(END(msg), "Ramdac:     %s\n", ramdac);
    if (card[_key].dacspeed)
	sprintf(END(msg), "DacSpeed:     %s\n", card[_key].dacspeed);
    if (clockchip)
	sprintf(END(msg), "Clockchip:  %s\n", clockchip);
    if (card[_key].flags & NOCLOCKPROBE)
	sprintf(END(msg),
		"Do NOT probe clocks or use any Clocks line.\n");
    if (card[_key].flags & UNSUPPORTED)
	strcat(END(msg), cardunsupported_text);

    if (didauto)
	strcat(END(msg),
		"\n"
		"Press [NO] if this is incorrect");

    while (1) {
	rc = dialog_yesno_extended(TITLE, msg, -1, -1,
			"NEXT", (char*)(didauto ? "NO" : "BACK"));
	if (rc != 0)
	    return 0;

	for (rc=FIRST_ACCEL; rc<NRSERVERS; rc++)
	    if (streq(card[_key].server, Server[rc].id)) {
		servertype = rc;
		break;
	    }
	if (askmemory())
	    return 1;
    }
    return askforserver();
} /* screenconfiguration */


int
VideoCard::askforserver()
{
    int rc;

    while (1) {
	for (rc = 0; rc < NRSERVERS; rc++)
	    Server[rc].selected = (rc == servertype);

	rc = Xmenu(TITLE,
"Now you must determine which server to run. Refer to the manpages and\n"
"other documentation. The following servers are available (they may not\n"
"all be installed on your system)\n", NRSERVERS, Server, NRSERVERS, 0, 0);

	if (rc < 0)
	    return 0;

	servertype = rc;
	if (askmemory())
	    return 1;
    }
} /* askforserver */


/*
 * memory_probe() starts up our chosen X server to see if it will
 * bother to tell us how much memory our video card has
 */
int
VideoCard::memory_probe()
{
    char command[400];
    char line[80];
    FILE *f;
    char *p;

    if (!configured())
	return 0;

    if (memory < 0) {
	memory = 0;
	/* if we've not detected memory, but we know what server
	 * to run, try firing it up to see what it detects
	 */
	dialog_msgbox(TITLE, "Autodetecting video memory.\n"
			     "       Please wait", -1, -1, 0);

	write_XF86Config(TMP_XF86CONFIG, this,
			 &generic_display,
			 &generic_geometry,
			 &generic_kb,
			 &generic_mouse);

	sprintf(command, "/usr/X11/bin/XF86_%s 2>&1 ", server());
	strcat(command, "-probeonly -allowMouseOpenFail -xf86config ");
	strcat(command, TMP_XF86CONFIG);

	if ( (f = popen(command, "r")) != 0 ) {
	    while ( fgets(line, sizeof line, f) != 0 )
		if ( (p = strstr(line, "videoram:"))
		  && (sscanf(p, "videoram: %d", &memory) == 1) )
		    break;
	    pclose(f);
	}
	unlink(TMP_XF86CONFIG);
    }
    return 1;
} /* memory_probe */


int
VideoCard::askmemory()
{
    int rc;
    char s[80];
    unsigned int x;

    if ( memory_probe() && memory > 0) {
	/* If we've already detected memory, don't ask around
	 */
	return _stage2 ? askcardid() : askramdac();
    }

    while (1) {
	rc = Xmenu(TITLE,
"You must indicate how much video memory you have.  If you encounter\n"
"problems that are due to the used server not supporting the amount\n"
"of memory you have (e.g. ATI Mach64 is limited to 1024K with the SVGA\n"
"server), specify the maximum amount supported by the server.",
	    NRVIDEOMEM, videomemory, NRVIDEOMEM, 0, 0);

	if (rc < 0)
	    return 0;

	if (rc == NRVIDEOMEM-1) {
	    /* CUSTOM */
	    rc = dialog_inputbox(TITLE,
				 "Amount of memory in Kbytes", -1, 40, s);
	    if (rc != 0)
		continue;
	    memory = atoi(s);
	}
	else
	    memory = atoi(videomemory[rc].id);

	if (askramdac())
	    return 1;
    }
} /* askmemory */


int
VideoCard::askcardid()
{
    int rc;
    char Vendor[80];
    char Model[80];

    if ( _stage2 ) {
	done();
	return 1;
    }

    strcpy(Vendor, card[_key].vendor() ? card[_key].vendor() : "");
    strcpy(Model,  card[_key].product() ? card[_key].product() : "");

    void *chain;

    chain = ObjChain(
	newString(0,0,40,40,Vendor,0, "Video card vendor |",0,0),
	newString(0,3,40,40,Model,0,  "Video card model  |",0,0));
    chain = ObjChain(chain, newOKButton(0, "NEXT", 0, 0));
    chain = ObjChain(chain, newCancelButton(1, "BACK", 0, 0));

    rc = MENU(chain, -1, -1, TITLE,
"You must now enter a few identification/description strings, namely\n"
"a vendor name, and a model name. Just pressing enter will fill in\n"
"default names (possibly from a card definition).", 0);

    deleteObjChain(chain);

    if (rc != MENU_OK)
	return 0;

    card[_key].vendor((char*)(Vendor[0] ? Vendor : "Unknown"));
    card[_key].product((char*)(Model[0] ? Model : "Unknown"));
    done();
    return 1;
} /* askcardid */


/*
 * Handle the Ramdac/Clockchip setting.
 */
int
VideoCard::askramdac()
{
    int rc;

    static ListItem ramdactable[] =
    {
	{ 0,	 	"Do not specify RAMDAC",  },
	{ "att20c490",	"AT&T 20C490 (S3 and AGX servers, ARK driver)",  },
	{ "att20c498",	"AT&T 20C498/21C498/22C498 (S3, autodetected)",  },
	{ "att20c409",	"AT&T 20C409/20C499 (S3, autodetected)",  },
	{ "att20c505",	"AT&T 20C505 (S3)",  },
	{ "bt481",	"BrookTree BT481 (AGX)",  },
	{ "bt482",	"BrookTree BT482 (AGX)",  },
	{ "bt485",	"BrookTree BT485/9485 (S3)",  },
	{ "sc15025",	"Sierra SC15025 (S3, AGX)",  },
	{ "s3gendac",	"S3 GenDAC (86C708) (autodetected)",  },
	{ "s3_sdac",	"S3 SDAC (86C716) (autodetected)",  },
	{ "stg1700",	"STG-1700 (S3, autodetected)",  },
	{ "stg1703",	"STG-1703 (S3, autodetected)",  },
	{ "ti3020",	"TI 3020 (S3, autodetected)",  },
	{ "ti3025",	"TI 3025 (S3, autodetected)",  },
	{ "ti3026",	"TI 3026 (S3, autodetected)",  },
	{ "ibm_rgb514",	"IBM RGB 514 (S3, autodetected)",  },
	{ "ibm_rgb524",	"IBM RGB 524 (S3, autodetected)",  },
	{ "ibm_rgb525",	"IBM RGB 525 (S3, autodetected)",  },
	{ "ibm_rgb526",	"IBM RGB 526 (S3)",  },
	{ "ibm_rgb528",	"IBM RGB 528 (S3, autodetected)",  },
	{ "ics5342",	"ICS5342 (S3, ARK)",  },
	{ "ics5341",	"ICS5341 (W32)",  },
	{ "zoomdac",	"IC Works w30C516 ZoomDac (ARK)",  },
    };
#define NR_RAMDAC	(sizeof ramdactable / sizeof ramdactable[0])

    ramdac = 0;
    if (servertype <= SVGA || _stage2)
	return askcardid();

    /* If this card has a ramdac already, we'll conveniently make it
     * the default choice
     */
    if (card[_key].ramdac) {
	char *rd = card[_key].ramdac;

	for (int x=0; x<NR_RAMDAC; x++)
	    ramdactable[x].selected = streq(rd, ramdactable[x].id);
    }

    while (1) {
	rc = Xmenu(TITLE,
"The RAMDAC setting only applies to the S3, AGX, W32 servers, and some\n"
"drivers in the SVGA servers. Some RAMDAC's are auto-detected by the\n"
"server.  The detection of a RAMDAC is forced by using a Ramdac\n"
"\"identifier\" line in the Device section. The identifiers are shown at\n"
"the right of the following table of RAMDAC types",
		10, ramdactable, NR_RAMDAC, 0, 0);

	if (rc < 0)
	    return 0;

	ramdac = ramdactable[rc].id;

	if (askclockchip())
	    return 1;
    }
} /* askramdac */


/*
 * ask about the clock
 */
int
VideoCard::askclockchip()
{
    int rc;
    unsigned int x;
    char *cc;

    static ListItem clockchiptable[] = {
	{ 0,		"Do not specify clockchip", },
	{ "ch8391",	"Chrontel 8391", },
	{ "icd2061a",	"ICD2061A and compatibles (ICS9161A, DCS2824)", },
	{ "ics2595",	"ICS2595", },
	{ "ics5342",	"ICS5342 (similar to SDAC, but not completely compatible)", },
	{ "ics5341",	"ICS5341", },
	{ "s3gendac",	"S3 GenDAC (86C708) and ICS5300 (autodetected)", },
	{ "s3_sdac",	"S3 SDAC (86C716)", },
	{ "stg1703",	"STG 1703 (autodetected)", },
	{ "sc11412",	"Sierra SC11412", },
	{ "ti3025",	"TI 3025 (autodetected)", },
	{ "ti3026",	"TI 3026 (autodetected)", },
	{ "ibm_rgb5xx",	"IBM RGB 51x/52x (autodetected)", },
};
#define NR_CLOCKCHIP	((sizeof clockchiptable/sizeof clockchiptable[0]))

    clockchip = 0;

    if (cc = card[_key].clockchip)
	for (x=0; x<NR_CLOCKCHIP; x++)
	    clockchiptable[x].selected = streq(cc, clockchiptable[x].id);

    while (1) {
	rc = Xmenu(TITLE,
"A Clockchip line in the Device section forces the detection of a\n"
"programmable clock device. With a clockchip enabled, any required clock\n"
"can be programmed without requiring probing of clocks or a Clocks line.\n"
"Most cards don't have a programmable clock chip.",
	    10, clockchiptable, NR_CLOCKCHIP, 0, 0);

	if (rc < 0)
	    return 0;

	clockchip = clockchiptable[rc].id;
	if (clockprobe())
	    return 1;
    }
} /* askclockchip */


/*
 * optionally run the server to detect clock settings
 */
int
VideoCard::clockprobe()
{
    int rc;
    char s[80];
    static int blurb = 1;

    if (numberofclocks > 0 || (card[_key].flags & NOCLOCKPROBE) || getuid() != 0)
	return askcardid();

    if (blurb) {
	rc = Xmesg(TITLE,
"For most configurations, a Clocks line is useful since it prevents the\n"
"slow and nasty sounding clock probing at server start-up. Probed clocks\n"
"are displayed at server startup, along with other server and hardware\n"
"configuration info. You can save this information in a file by running\n"
"'X -probeonly 2>output_file'. Be warned that clock probing is inherently\n"
"imprecise; some clocks may be slightly too high (varies per run).");

	if (rc != 0)
	    return 0;
	blurb = 0;
    }

    while (1) {
	rc = dialog_yesno(TITLE,
"At this point I can run X -probeonly, and try to extract the clock\n"
"information from the output. It is recommended that you do this yourself\n"
"and add a clocks line (note that the list of clocks may be split over\n"
"multiple Clocks lines) to your Device section afterwards. Be aware that\n"
"a clocks line is not appropriate for drivers that have a fixed set of\n"
"clocks and don't probe by default (e.g. Cirrus). Also, for the P9000\n"
"server you must simply specify clocks line that matches the modes you\n"
"want to use.  For the S3 server with a programmable clock chip you need\n"
"a 'ClockChip' line and no Clocks line.\n"
"\n"
"It is possible that the hardware detection routines in the server will\n"
"somehow cause the system to crash and the screen to remain blank. If\n"
"this is the case, do not choose this option the next time. The server\n"
"may need a Ramdac, ClockChip or special option (e.g. \"nolinear\" for\n"
"S3) to probe and start-up correctly.\n"
"\n"
"Do you want me to run 'X -probeonly' now?",
	    -1, -1);

	if (rc != 0)
	    return askcardid();

	/*
	 * Write temporary XF86Config and run X -probeonly.
	 * Only allow when root.
	 */
	FILE *f;
	char syscmdline[1024];		/* enough */

	dialog_msgbox(TITLE, "Probing for clocks", -1, 50, 0);

	write_XF86Config(TMP_XF86CONFIG, this,
			 &generic_display,
			 &generic_geometry,
			 &generic_kb,
			 &generic_mouse);
	sync();
	/* compose a line with the real path */
	sprintf(syscmdline,
		"/usr/X11R6/bin/XF86_%s -probeonly -pn -xf86config "
		TMP_XF86CONFIG " 2>&1 | /bin/grep clocks\\:",
		server());

	if ((f = popen(syscmdline, "r")) != 0) {
	    char buf[800];
	    /* Parse lines. */
	    while (fgets(buf, sizeof buf, f) != NULL) {
		char msg[200];
		char *clks;
		clks = strstr(buf, "clocks: ") + 8;
		if (clks >= buf + 3 && streq(clks - 11, "num"))
		    /* Reject lines with 'numclocks:'. */
		    continue;
		if (clks >= buf + 8 && streq(clks - 14, "pixel "))
		    /* Reject lines with 'pixel clocks:'. */
		    continue;
		clks[strlen(clks) - 1] = '\0';	/* Remove '\n'. */
		clocks[numberofclocks] = strdup(clks);
		sprintf(msg, "Clocks %s", clks);
		dialog_msgbox(TITLE, msg, -1, 50, 0);
		dialog_notify(msg);
		numberofclocks++;
	    }
	    pclose(f);
	    if (numberofclocks == 0)
		Error("No clocks information found");
	}
	else {
	    Error("X -probeonly call failed.\n"
		  "No Clocks line inserted.");
	    return askcardid();
	}
	unlink(TMP_XF86CONFIG);

	return askcardid();
    }
} /* clockprobe */
