package Xconfigurator; use diagnostics; use strict; use vars qw($in $install $resolution_wanted @depths %depths @resolutions @svgaservers @accelservers @allservers %videomemory @ramdac_name @ramdac_id @clockchip_name @clockchip_id %keymap_translate @vsync_range %standard_monitors $intro_text $finalcomment_text $s3_comment $cirrus_comment $probeonlywarning_text $monitorintro_text $hsyncintro_text $vsyncintro_text $XF86firstchunk_text $keyboardsection_start $keyboardsection_part2 $keyboardsection_end $pointersection_text1 $pointersection_text2 $monitorsection_text1 $monitorsection_text2 $monitorsection_text3 $monitorsection_text4 $modelines_text_Trident_TG_96xx $modelines_text $devicesection_text $screensection_text1); use pci_probing::main; use common qw(:common :file); use log; use Xconfigurator_consts; use my_gtk qw(:wrappers); my $tmpconfig = "/tmp/Xconfig"; my ($prefix, %cards, %monitors); 1; sub setVirtual($) { my $vt = ''; local *C; sysopen C, "/dev/console", 2 or die "failed to open /dev/console: $!"; ioctl(C, c::VT_GETSTATE(), $vt) or die "ioctl VT_GETSTATE failed"; ioctl(C, c::VT_ACTIVATE(), $_[0]) or die "ioctl VT_ACTIVATE failed"; ioctl(C, c::VT_WAITACTIVE(), $_[0]) or die "ioctl VT_WAITACTIVE failed"; unpack "S", $vt; } sub readCardsDB { my ($file) = @_; my ($card); %cards and return; local *F; open F, $file or die "file $file not found"; my ($lineno, $cmd, $val) = 0; my $fs = { LINE => sub { push @{$card->{lines}}, $val unless $val eq "VideoRam" }, NAME => sub { $cards{$card->{type}} = $card if $card; $card = { type => $val }; }, SEE => sub { my $c = $cards{$val} or die "Error in database, invalid reference $val at line $lineno"; push @{$card->{lines}}, @{$c->{lines} || []}; add2hash($card->{flags}, $c->{flags}); add2hash($card, $c); }, CHIPSET => sub { $card->{chipset} = $val; $card->{flags}{needVideoRam} = 1 if member($val, qw(mgag10 mgag200 RIVA128)); }, SERVER => sub { $card->{server} = $val; }, RAMDAC => sub { $card->{ramdac} = $val; }, DACSPEED => sub { $card->{dacspeed} = $val; }, CLOCKCHIP => sub { $card->{clockchip} = $val; $card->{flags}{noclockprobe} = 1; }, NOCLOCKPROBE => sub { $card->{flags}{noclockprobe} = 1 }, UNSUPPORTED => sub { $card->{flags}{unsupported} = 1 }, COMMENT => sub {}, }; foreach () { $lineno++; s/\s+$//; /^#/ and next; /^$/ and next; /^END/ and last; ($cmd, $val) = /(\S+)\s*(.*)/ or log::l("bad line $lineno ($_)"), next; my $f = $fs->{$cmd}; $f ? &$f() : log::l("unknown line $lineno ($_)"); } push @{$cards{S3}{lines}}, $s3_comment; push @{$cards{'CL-GD'}{lines}}, $cirrus_comment; #- this entry is broken in X11R6 cards db $cards{I128}{flags}{noclockprobe} = 1; } sub readMonitorsDB { my ($file) = @_; %monitors and return; local *F; open F, $file or die "can't open monitors database ($file): $!"; my $lineno = 0; foreach () { $lineno++; s/\s+$//; /^#/ and next; /^$/ and next; my @fields = qw(type bandwidth hsyncrange vsyncrange); my @l = split /\s*;\s*/; @l == @fields or log::l("bad line $lineno ($_)"), next; my %l; @l{@fields} = @l; $monitors{$l{type}} = \%l; } while (my ($k, $v) = each %standard_monitors) { $monitors{$k} = $monitors{$v->[0]} = { hsyncrange => $v->[1], vsyncrange => $v->[2] }; } } sub rewriteInittab { my ($runlevel) = @_; { local (*F, *G); open F, "$prefix/etc/inittab" or die "cannot open $prefix/etc/inittab: $!"; open G, "> $prefix/etc/inittab-" or die "cannot write in $prefix/etc/inittab-: $!"; foreach () { print G /^(id:)[35](:initdefault:)\s*$/ ? "$1$runlevel$2\n" : $_; # ** } } unlink("$prefix/etc/inittab"); rename("$prefix/etc/inittab-", "$prefix/etc/inittab"); } sub keepOnlyLegalModes { my ($card) = @_; my $mem = 1024 * ($card->{memory} || return); while (my ($depth, $res) = each %{$card->{depth}}) { @$res = grep { $mem >= product(@$_, $depth / 8) } @$res; } } sub cardConfigurationAuto() { my $card; if (my ($c) = pci_probing::main::probe("DISPLAY")) { local $_; ($card->{identifier}, $_) = @$c; $card->{type} = $1 if /Card:(.*)/; $card->{server} = $1 if /Server:(.*)/; } $card; } sub cardConfiguration(;$$) { my ($card, $noauto) = @_; $card ||= {}; readCardsDB("$prefix/usr/X11R6/lib/X11/Cards"); add2hash($card, $cards{$card->{type}}) if $card->{type}; #- try to get info from given type $card->{type} = undef unless $card->{server}; #- bad type as we can't find the server add2hash($card, cardConfigurationAuto()) unless $card->{server} || $noauto; add2hash($card, { type => $in->ask_from_list('', _("Choose a graphic card"), [keys %cards]) }) unless $card->{type} || $card->{server}; add2hash($card, $cards{$card->{type}}) if $card->{type}; add2hash($card, { vendor => "Unknown", board => "Unknown" }); $card->{prog} = "/usr/X11R6/bin/XF86_$card->{server}"; -x "$prefix$card->{prog}" or !defined $install or &$install($card->{server}); -x "$prefix$card->{prog}" or die "server $card->{server} is not available (should be in $prefix$card->{prog})"; unless ($::testing) { unlink("$prefix/etc/X11/X"); symlink("../..$card->{prog}", "$prefix/etc/X11/X"); } unless ($card->{type}) { $card->{flags}{noclockprobe} = member($card->{server}, qw(I128 S3 S3V Mach64)); } $card->{flags}{needVideoRam} and $card->{memory} ||= $videomemory{$in->ask_from_list_('', _("Select the memory size of your graphic card"), [ sort { $videomemory{$a} <=> $videomemory{$b} } keys %videomemory])}; $card; } sub monitorConfiguration(;$) { my $monitor = shift || {}; $monitor->{hsyncrange} && $monitor->{vsyncrange} and return $monitor; readMonitorsDB(-e "MonitorsDB" ? "MonitorsDB" : "/usr/share/MonitorsDB"); add2hash($monitor, { type => $in->ask_from_list('', _("Choose a monitor"), [keys %monitors]) }) unless $monitor->{type}; add2hash($monitor, $monitors{$monitor->{type}}); add2hash($monitor, { type => "Unknown", vendor => "Unknown", model => "Unknown" }); $monitor; } sub testConfig($) { my ($o) = @_; my ($resolutions, $clocklines); write_XF86Config($o, $tmpconfig); local *F; open F, "$prefix$o->{card}{prog} :9 -probeonly -pn -xf86config $tmpconfig 2>&1 |"; foreach () { $o->{card}{memory} ||= $2 if /(videoram|Video RAM):\s*(\d*)/; # look for clocks push @$clocklines, $1 if /clocks: (.*)/ && !/(pixel |num)clocks:/; push @$resolutions, [ $1, $2 ] if /: Mode "(\d+)x(\d+)": mode clock/; print; } close F or die "X probeonly failed"; ($resolutions, $clocklines); } sub testFinalConfig($;$) { my ($o, $auto) = @_; $o->{monitor}{hsyncrange} && $o->{monitor}{vsyncrange} or $in->ask_warn('', _("Monitor not configured")), return; $o->{card}{server} or $in->ask_warn('', _("Graphic card not configured yet")), return; $o->{card}{depth} or $in->ask_warn('', _("Resolutions not chosen yet")), return; rename("$prefix/etc/X11/XF86Config", "$prefix/etc/X11/XF86Config.old") || die "unable to make a backup of XF86Config" unless $::testing; write_XF86Config($o, $::testing ? $tmpconfig : "$prefix/etc/X11/XF86Config"); $auto or $in->ask_yesorno(_("Test configuration"), _("Do you want to test the configuration?")) or return 1; my $pid; unless ($pid = fork) { my @l = "X"; @l = ($o->{card}{prog}, "-xf86config", $tmpconfig) if $::testing; chroot $prefix if $prefix; exec @l, ":9" or exit 1; } do { sleep 1; } until (c::Xtest(':0')); #- create a link from the non-prefixed /tmp/.X11-unix/X9 to the prefixed one #- that way, you can talk to :9 without doing a chroot unlink "/tmp/.X11-unix/X9" if $prefix; symlink "$prefix/tmp/.X11-unix/X9", "/tmp/.X11-unix/X9" if $prefix; local *F; open F, "|perl" or die ''; print F "use lib qw(", join(' ', @INC), ");\n"; print F q{ use interactive_gtk; use my_gtk qw(:wrappers); $ENV{DISPLAY} = ":9"; gtkset_mousecursor(68); gtkset_background(200, 210, 210); my ($h, $w) = Gtk::Gdk::Window->new_foreign(Gtk::Gdk->ROOT_WINDOW)->get_size; $my_gtk::force_position = [ $w / 3, $h / 2.4 ]; $my_gtk::force_focus = 1; my $text = Gtk::Label->new; my $time = 8; Gtk->timeout_add(1000, sub { $text->set(_("(leaving in %d seconds)", $time)); $time-- or Gtk->main_quit; }); exit (interactive_gtk->new->ask_yesorno('', [ _("Is this ok?"), $text ], 1) ? 0 : 222); }; my $rc = close F; my $err = $?; unlink "/tmp/.X11-unix/X9" if $prefix; kill 2, $pid; $rc || $err == 222 << 8 or $in->ask_warn('', _("An error occurred, try changing some parameters")); $rc; } sub autoResolutions($;$) { my ($o, $nowarning) = @_; my $card = $o->{card}; $nowarning || $in->ask_okcancel(_("Automatic resolutions"), _("To find the available resolutions i will try different ones. Your screen will blink... You can switch if off if you want, you'll hear a beep when it's over")) or return; #- swith to virtual console 1 (hopefully not X :) my $vt = setVirtual(1); #- Configure the modes order. my ($ok, $best); foreach (reverse @depths) { local $card->{default_depth} = $_; my ($resolutions, $clocklines) = eval { testConfig($o) }; if ($@ || !$resolutions) { delete $card->{depth}{$_}; } else { $card->{clocklines} ||= $clocklines unless $card->{flags}{noclockprobe}; $card->{depth}{$_} = [ @$resolutions ]; } } #- restore the virtual console setVirtual($vt); print "\a"; #- beeeep! } sub autoDefaultDepth($$) { my ($card, $resolution_wanted) = @_; my ($wres_wanted) = split 'x', $resolution_wanted; my ($best, $depth); while (my ($d, $r) = each %{$card->{depth}}) { $depth = $depth ? max($depth, $d) : $d; # try to have $resolution_wanted $best = $best ? max($best, $d) : $d if $r->[0][0] >= $wres_wanted; } $best || $depth or die "no valid modes"; } sub chooseResolutions($$) { my ($card, $chosen_depth) = @_; my $W = my_gtk->new(_("Resolution")); my %txt2depth = reverse %depths; my $chosen_w = 9999999; #- will be set by the combo callback my ($r, $depth_combo, %w2depth, %w2h, %w2widget); my $set_depth = sub { $depth_combo->entry->set_text(translate($depths{$chosen_depth})) }; #- the set function is usefull to toggle the CheckButton with the callback being ignored my $ignore; my $set = sub { $ignore = 1; $_[0]->set_active(1); $ignore = 0; }; while (my ($depth, $res) = each %{$card->{depth}}) { foreach (@$res) { $w2h{$_->[0]} = $_->[1]; push @{$w2depth{$_->[0]}}, $depth; } } while (my ($w, $h) = each %w2h) { my $V = $w . "x" . $h; $w2widget{$w} = $r = new Gtk::RadioButton($r ? ($V, $r) : $V); $r->signal_connect("clicked" => sub { $ignore and return; $chosen_w = $w; unless (member($chosen_depth, @{$w2depth{$w}})) { $chosen_depth = max(@{$w2depth{$w}}); &$set_depth(); } }); } gtkadd($W->{window}, gtkpack_($W->create_box_with_title(_("Choose resolution and color depth")), 1, gtkpack(new Gtk::HBox(0,20), $depth_combo = new Gtk::Combo, gtkpack_(new Gtk::VBox(0,0), map { 0, $w2widget{$_} } ikeys(%w2widget), ), ), 0, $W->create_okcancel, )); $depth_combo->disable_activate; $depth_combo->set_use_arrows_always(1); $depth_combo->entry->set_editable(0); $depth_combo->set_popdown_strings(map { translate($depths{$_}) } ikeys(%{$card->{depth}})); $depth_combo->entry->signal_connect(changed => sub { $chosen_depth = $txt2depth{untranslate($depth_combo->entry->get_text, keys %txt2depth)}; my $w = $card->{depth}{$chosen_depth}[0][0]; $chosen_w > $w and &$set($w2widget{$chosen_w = $w}); }); &$set_depth(); $W->main or return; ($chosen_depth, $chosen_w); } sub resolutionsConfiguration($$) { my ($o, $option) = @_; my $card = $o->{card}; my $auto = $option eq 'auto'; my $nowarning = $auto || $option eq 'nowarning'; my $noauto = $option eq 'noauto'; #- For the mono and vga16 server, no further configuration is required. if (member($card->{server}, "Mono", "VGA16")) { $card->{depth}{8} = [[ 640, 480 ]]; return; } #- some of these guys hate to be poked #- if we dont know then its at the user's discretion #-my $manual ||= #- $card->{server} =~ /^(TGA|Mach32)/ || #- $card->{name} =~ /^Riva 128/ || #- $card->{chipset} =~ /^(RIVA128|mgag)/ || #- $::expert; #- #-my $unknown = #- member($card->{server}, qw(S3 S3V I128 Mach64)) || #- member($card->{type}, #- "Matrox Millennium (MGA)", #- "Matrox Millennium II", #- "Matrox Millennium II AGP", #- "Matrox Mystique", #- "Matrox Mystique", #- "S3", #- "S3V", #- "I128", #- ) || #- $card->{type} =~ /S3 ViRGE/; #- #-$unknown and $manual ||= !$in->ask_okcancel('', [ _("I can try to autodetect information about graphic card, but it may freeze :("), #- _("Do you want to try?") ]); if (is_empty_hash_ref($card->{depth})) { $card->{depth}{$_} = [ map { [ split "x" ] } @resolutions ] foreach @depths; if ($nowarning || (!$noauto && $in->ask_okcancel(_("Automatic resolutions"), _("I can try to find the available resolutions (eg: 800x600). Alas it can freeze sometimes Do you want to try?")))) { autoResolutions($o, $nowarning); is_empty_hash_ref($card->{depth}) and $in->ask_warn('', _("No valid modes found Try with another video card or monitor")), return; } } #- sort resolutions in each depth foreach (values %{$card->{depth}}) { my $i; @$_ = grep { first($i != $_->[0], $i = $_->[0]) } sort { $b->[0] <=> $a->[0] } @$_; } #- remove unusable resolutions (based on the video memory size) keepOnlyLegalModes($card); my $res = $o->{resolution_wanted} || $resolution_wanted; my $depth = eval { $card->{default_depth} || autoDefaultDepth($card, $res) }; $auto or ($depth, $res) = chooseResolutions($card, $depth) or return; #- needed in auto mode when all has been provided by the user $card->{depth}{$depth} or die "you fixed an unusable depth"; #- remove all biggest resolution (keep the small ones for ctl-alt-+) #- otherwise there'll be a virtual screen :( $card->{depth}{$depth} = [ grep { $_->[0] <= $res } @{$card->{depth}{$depth}} ]; $card->{default_depth} = $depth; 1; } #- Create the XF86Config file. sub write_XF86Config { my ($o, $file) = @_; my $O; local *F; open F, ">$file" or die "can't write XF86Config in $file: $!"; print F $XF86firstchunk_text; #- Write keyboard section. $O = $o->{keyboard}; print F $keyboardsection_start; print F " RightAlt ", ($O->{altmeta} ? "ModeShift" : "Meta"), "\n"; print F $keyboardsection_part2; print F qq( XkbLayout "$O->{xkb_keymap}"\n); print F $keyboardsection_end; #- Write pointer section. $O = $o->{mouse}; print F $pointersection_text1; print F qq( Protocol "$O->{XMOUSETYPE}"\n); print F qq( Device "$O->{device}"\n); #- this will enable the "wheel" or "knob" functionality if the mouse supports it print F " ZAxisMapping 4 5\n" if member($O->{XMOUSETYPE}, qw(IntelliMouse IMPS/2 ThinkingMousePS/2 NetScrollPS/2 NetMousePS/2 MouseManPlusPS/2)); print F $pointersection_text2; print F "#" unless $O->{emulate3buttons}; print F " Emulate3Buttons\n"; print F "#" unless $O->{emulate3buttons}; print F " Emulate3Timeout 50\n\n"; print F "# ChordMiddle is an option for some 3-button Logitech mice\n\n"; print F "#" unless $O->{chordmiddle}; print F " ChordMiddle\n\n"; print F " ClearDTR\n" if $O->{cleardtrrts}; print F " ClearRTS\n\n" if $O->{cleardtrrts}; print F "EndSection\n\n\n"; #- Write monitor section. $O = $o->{monitor}; $O->{modelines} ||= $o->{card}{type} eq "TG 96" ? $modelines_text_Trident_TG_96xx : $modelines_text; print F $monitorsection_text1; print F qq( Identifier "$O->{type}"\n); print F qq( VendorName "$O->{vendor}"\n); print F qq( ModelName "$O->{model}"\n); print F "\n"; print F $monitorsection_text2; print F qq( HorizSync $O->{hsyncrange}\n); print F "\n"; print F $monitorsection_text3; print F qq( VertRefresh $O->{vsyncrange}\n); print F "\n"; print F $monitorsection_text4; print F $O->{modelines} || ($o->{card}{type} eq "TG 96" ? $modelines_text_Trident_TG_96xx : $modelines_text); print F "\nEndSection\n\n\n"; #- Write Device section. $O = $o->{card}; print F $devicesection_text; print F qq(Section "Device"\n); print F qq( Identifier "$O->{type}"\n); print F qq( VendorName "$O->{vendor}"\n); print F qq( BoardName "$O->{board}"\n); print F "#" if $O->{memory} && !$O->{flags}{needVideoRam}; print F " VideoRam $O->{memory}\n" if $O->{memory}; print F map { " $_\n" } @{$O->{lines} || []}; print F qq( Ramdac "$O->{ramdac}"\n) if $O->{ramdac}; print F qq( Dacspeed "$O->{dacspeed}"\n) if $O->{dacspeed}; if ($O->{clockchip}) { print F qq( Clockchip "$O->{clockchip}"\n); } else { print F " # Clock lines\n"; print F " Clocks $_\n" foreach (@{$O->{clocklines}}); } print F "EndSection\n\n\n"; #- Write Screen sections. print F $screensection_text1; my $screen = sub { my ($server, $defdepth, $device, $depths) = @_; print F qq( Section "Screen" Driver "$server" Device "$device" Monitor "$o->{monitor}{type}" ); print F " DefaultColorDepth $defdepth\n" if $defdepth; foreach (ikeys(%$depths)) { my $m = join(" ", map { qq("$_->[0]x$_->[1]") } @{$depths->{$_}}); print F qq( Subsection "Display"\n); print F qq( Depth $_\n) if $_; print F qq( Modes $m\n); print F qq( ViewPort 0 0\n); print F qq( EndSubsection\n); } print F "EndSection\n"; }; #- SVGA screen section. print F qq( # The Colour SVGA server ); if (member($O->{server}, @svgaservers)) { &$screen("svga", $O->{default_depth}, $O->{type}, $O->{depth}); } else { &$screen("svga", '', "Generic VGA", { 8 => [[ 320, 200 ]] }); } &$screen("vga16", '', (member($O->{server}, "Mono", "VGA16") ? $O->{type} : "Generic VGA"), { '' => [[ 640, 480 ], [ 800, 600 ]]}); &$screen("vga2", '', (member($O->{server}, "Mono", "VGA16") ? $O->{type} : "Generic VGA"), { '' => [[ 640, 480 ], [ 800, 600 ]]}); &$screen("accel", $O->{default_depth}, $O->{type}, $O->{depth}); } sub XF86check_link { my ($void) = @_; my $f = "$prefix/etc/X11/XF86Config"; touch($f); my $l = "$prefix/usr/X11R6/lib/X11/XF86Config"; if (-e $l && (stat($f))[1] != (stat($l))[1]) { #- compare the inode, must be the sames -e $l and unlink($l) || die "can't remove bad $l"; symlink "../../../../etc/X11/XF86Config", $l; } } sub show_info { my ($o) = @_; my $info; $info .= _("Keyboard layout: %s\n", $o->{keyboard}{xkb_keymap}); $info .= _("Mouse type: %s\n", $o->{mouse}{XMOUSETYPE}); $info .= _("Mouse device: %s\n", $o->{mouse}{device}) if $::expert; $info .= _("Monitor: %s\n", $o->{monitor}{type}); $info .= _("Monitor HorizSync: %s\n", $o->{monitor}{hsyncrange}) if $::expert; $info .= _("Monitor VertRefresh: %s\n", $o->{monitor}{vsyncrange}) if $::expert; $info .= _("Graphic card: %s\n", $o->{card}{type}); $info .= _("Graphic memory: %s KB\n", $o->{card}{memory}) if $o->{card}{memory}; $info .= _("XFree86 server: %s\n", $o->{card}{server}); $in->ask_warn('', $info); } #- Program entry point. sub main { my $o; ($prefix, $o, $in, $install) = @_; $o ||= {}; XF86check_link(); $o->{card} = cardConfiguration($o->{card}, $::noauto); $o->{monitor} = monitorConfiguration($o->{monitor}); my $ok = resolutionsConfiguration($o, $::auto && 'auto' || $::noauto && 'noauto' || ''); $ok &&= testFinalConfig($o, $::auto); my $quit; until ($ok || $quit) { my %c = my @c = ( __("Change Monitor") => sub { $o->{monitor} = monitorConfiguration() }, __("Change Graphic card") => sub { $o->{card} = cardConfiguration('', 'noauto') }, __("Change Resolution") => sub { resolutionsConfiguration($o, 'noauto') }, __("Automatical resolutions search") => sub { delete $o->{card}{depth}; resolutionsConfiguration($o, 'nowarning'); }, __("Show information") => sub { show_info($o) }, __("Test again") => sub { $ok = testFinalConfig($o, 1) }, __("Quit") => sub { $quit = 1 }, ); &{$c{$in->ask_from_list_('', _("What do you want to do?"), [ grep { !ref } @c ])}}; } if ($ok) { my $run = $o->{xdm} || $::auto || $in->ask_yesorno(_("X at startup"), _("I can set up your computer to automatically start X upon booting. Would you like X to start when you reboot?")); rewriteInittab($run ? 5 : 3) unless $::testing; $in->ask_warn(_("X successfully configured"), _("Configuration file has been written. Take a look at it before running 'startx'. Within the server press ctrl, alt and '+' simultaneously to cycle video resolutions. Pressing ctrl, alt and backspace simultaneously immediately exits the server For further configuration, refer to /usr/X11R6/lib/X11/doc/README.Config.")) unless $::auto; } }