#!/usr/bin/perl # -*- coding: utf-8 -*- use utf8; use strict; use diagnostics; use lib qw(/usr/lib/libDrakX); use standalone; use MDK::Common; # help perl_checker use common; # i18n: IMPORTANT: to get correct namespace (drakconf instead of only libDrakX) BEGIN { unshift @::textdomains, 'drakconf' } use mygtk3 qw(gtknew); #- do not import gtkadd which conflicts with ugtk3 version use ugtk3 qw(:create :helpers :wrappers); use interactive; use modules::parameters; use harddrake::data; #- needs to stay after use-ugtk3 as long as this module defines globals containing some N() use fsedit; use pkgs; use POSIX qw(:sys_wait_h); # { field => [ short_translation, full_description] } my %fields = ( generic => { "alternative_drivers" => [ N("Alternative drivers"), N("the list of alternative drivers for this sound card") ], "bus" => [ N("Bus"), N("this is the physical bus on which the device is plugged (eg: PCI, USB, ...)") ], "bus_id" => [ N("Bus identification"), N("- PCI and USB devices: this lists the vendor, device, subvendor and subdevice PCI/USB ids") ], "bus_location" => [ N("Location on the bus"), N("- pci devices: this gives the PCI slot, device and function of this card - eide devices: the device is either a slave or a master device - scsi devices: the scsi bus and the scsi device ids") ], "capacity" => [ N("Drive capacity"), N("special capacities of the driver (burning ability and or DVD support)") ], "description" => [ N("Description"), N("this field describes the device") ], "device" => [ N("Old device file"), N("old static device name used in dev package") ], "driver" => [ #-PO: here "module" is the "jargon term" for a kernel driver N("Module"), N("the module of the GNU/Linux kernel that handles the device") ], "extended_partitions" => [ N("Extended partitions"), N("the number of extended partitions") ], "geometry" => [ N("Geometry"), N("Cylinder/head/sectors geometry of the disk") ], "host" => [ N("Disk controller"), N("the disk controller on the host side") ], "info" => [ N("Identifier"), N("usually the device serial number") ], "media_type" => [ N("Media class"), N("class of hardware device") ], "Model" => [ N("Model"), N("hard disk model") ], "port" => [ N("Port"), N("network printer port") ], "primary_partitions" => [ N("Primary partitions"), N("the number of the primary partitions") ], "Vendor" => [ N("Vendor"), N("the vendor name of the device") ], "pci_domain" => [ N("PCI domain"), N("the PCI domain of the device") ], "pci_revision" => [ N("PCI revision"), N("the PCI domain of the device") ], "pci_bus" => [ N("Bus PCI #"), N("the PCI bus on which the device is plugged") ], "pci_device" => [ N("PCI device #"), N("PCI device number") ], "pci_function" => [ N("PCI function #"), N("PCI function number") ], "vendor" => [ N("Vendor ID"), N("this is the standard numerical identifier of the vendor") ], "id" => [ N("Device ID"), N("this is the numerical identifier of the device") ], "subvendor" => [ N("Sub vendor ID"), N("this is the minor numerical identifier of the vendor") ], "subid" => [ N("Sub device ID"), N("this is the minor numerical identifier of the device") ], "usb_pci_device" =>, [ N("Device USB ID"), N("..") ], }, CPU => { "system type" => [ "Sytem Type", "Name of the system" ], "BogoMIPS" => [ N("Bogomips"), N("the GNU/Linux kernel needs to run a calculation loop at boot time to initialize a timer counter. Its result is stored as bogomips as a way to \"benchmark\" the cpu.") ], "bogomips" => [ N("Bogomips"), N("the GNU/Linux kernel needs to run a calculation loop at boot time to initialize a timer counter. Its result is stored as bogomips as a way to \"benchmark\" the cpu.") ], "cache size" => [ N("Cache size"), N("size of the (second level) cpu cache") ], "cpu family" => [ N("Cpuid family"), N("family of the cpu (eg: 6 for i686 class)") ], "cpuid level" => [ N("Cpuid level"), N("information level that can be obtained through the cpuid instruction") ], "cpu MHz" => [ N("Frequency (MHz)"), N("the CPU frequency in MHz (Megahertz which in first approximation may be coarsely assimilated to number of instructions the cpu is able to execute per second)") ], "flags" => [ N("Flags"), N("CPU flags reported by the kernel") ], "cpu cores" => [ N("Cores"), N("CPU cores") ], "core id" => [ N("Core ID"), N("Core ID") ], "physical id" => [ N("Physical ID"), N("Physical ID") ], "apicid" => [ N("ACPI ID"), N("ACPI ID") ], "siblings" => [ N("Siblings"), N("Siblings") ], "level" => [ N("Level"), N("sub generation of the cpu") ], "model" => [ N("Model"), N("generation of the cpu (eg: 8 for Pentium III, ...)") ], "model name" => [ N("Model name"), N("official vendor name of the cpu") ], "cpu model" => [ N("Model name"), N("official vendor name of the cpu") ], "name" => [ N("Name"), N("the name of the CPU") ], "processor" => [ N("Processor ID"), N("the number of the processor") ], "stepping" => [ N("Model stepping"), N("stepping of the cpu (sub model (generation) number)") ], "vendor_id" => [ N("Vendor"), N("the vendor name of the processor") ], "wp" => [ N("Write protection"), N("the WP flag in the CR0 register of the cpu enforce write protection at the memory page level, thus enabling the processor to prevent unchecked kernel accesses to user memory (aka this is a bug guard)") ], }, FLOPPY => { info => [ N("Floppy format"), N("format of floppies supported by the drive") ], }, HARDDISK => { channel => [ N("Channel"), N("EIDE/SCSI channel") ], info => [ N("Disk identifier"), N("usually the disk serial number") ], id => [ N("Target id number"), N("the SCSI target identifier") ], lun => [ N("Logical unit number"), N("the SCSI Logical Unit Number (LUN). SCSI devices connected to a host are uniquely identified by a channel number, a target id and a logical unit number") ], }, MEMORY => { 'Installed Size' => [ #-PO: here, "size" is the size of the ram chip (eg: 128Mo, 256Mo, ...) N("Installed size"), N("Installed size of the memory bank") ], 'Enabled Size' => [ N("Enabled Size"), N("Enabled size of the memory bank") ], 'name' => [ N("Type"), N("type of the memory device") ], 'Current Speed' => [ N("Speed"), N("Speed of the memory bank") ], 'Bank Connections' => [ N("Bank connections"), '' ], 'Socket Designation' => [ N("Name"), N("Socket designation of the memory bank") ], # for Memory Device: 'Locator' => [ N("Location"), ], 'Size' => [ N("Size"), N("Size of the memory device") ], 'Speed' => [ N("Speed"), N("Speed of the memory bank") ], }, MOUSE => { "device" => [ N("Device file"), N("the device file used to communicate with the kernel driver for the mouse") ], EmulateWheel => [ N("Emulated wheel"), N("whether the wheel is emulated or not") ], MOUSETYPE => [ N("Type"), N("the type of the mouse") ], name => [ N("Name"), N("the name of the mouse") ], nbuttons => [ N("Number of buttons"), N("the number of buttons the mouse has") ], type => [ N("Bus"), N("the type of bus on which the mouse is connected") ], Protocol => [ N("Mouse protocol used by X11"), N("the protocol that the graphical desktop use with the mouse") ], } ); my $identification = N("Identification"); my %groups = ( generic => { $identification => [ qw(Vendor model description info media_type) ], N("Connection") => [ qw(bus pci_domain pci_bus pci_device pci_function pci_revision vendor id subvendor subid) ], }, AUDIO => { N("Driver") => [ qw(driver alternative_drivers) ], }, CPU => { $identification => [ qw(processor vendor_id), "model name", "cpu family", qw(model level stepping), "cpuid level" ], N("Cores") => [ 'cpu cores', 'core id', 'physical id', 'apicid', 'siblings' ], N("Performances") => [ "cpu MHz", "cache size", "bogomips" ], }, HARDDISK => { $identification => [ qw(Vendor Model description info media_type) ], N("Connection") => [ qw(bus channel lun id) ], N("Bus identification") => [ qw(vendor id subvendor subid) ], N("Device") => [ qw(device) ], N("Partitions") => [ qw(primary_partitions extended_partitions) ], }, MOUSE => { $identification => [ qw(name type MOUSETYPE Protocol) ], N("Features") => [ qw(EmulateWheel nbuttons) ], }, ); foreach my $class (qw(BURNER CDROM DVDROM)) { $groups{$class} = $groups{HARDDISK}; $fields{$class} = $fields{HARDDISK}; } my ($in, $pid, $w); my (%options, %check_boxes); my $conffile = "/etc/sysconfig/harddrake2/ui.conf"; my ($current_device, $current_class, $current_configurator); my %sysh = distrib(); my $distro_name = $sysh{system}; my %menu_options = ( 'MODEMS_DETECTION' => N("Autodetect _modems"), 'PARALLEL_ZIP_DETECTION' => N("Autodetect parallel _zip drives"), ); $ugtk3::wm_icon = "harddrake"; $w = ugtk3->new(N("Hardware Configuration")); # fake diagnostics pragma: local $::main_window = $w->{real_window}; my $has_help = -x "/usr/sbin/drakhelp_inst"; my $ui = gtknew('UIManager', actions => [ # [name, stock_id, value, label, accelerator, tooltip, callback] [ 'FileMenu', undef, N("_File") ], [ 'Quit', undef, N("_Quit"), N("Q"), undef, \&quit_global ], [ 'OptionsMenu', undef, N("_Options") ], [ 'HelpMenu', undef, N("_Help") ], if_($has_help, [ 'Help', undef, N("_Help"), N("H"), undef, \&run_help ]), [ 'Fields description', undef, N("_Fields description"), undef, undef, \&fields_help ], [ 'Report Bug', undef, N("_Report Bug"), undef, undef, \&run_drakbug ], [ 'About', undef, N("_About..."), '', undef, \&about ], ], toggle_actions => [ [ 'MODEMS_DETECTION', undef, $menu_options{MODEMS_DETECTION}, undef, undef, \&handle_modem_option ], [ 'PARALLEL_ZIP_DETECTION', undef, $menu_options{PARALLEL_ZIP_DETECTION}, undef, undef, \&handle_zip_option ], ], string => join("\n", qq( ), if_($has_help, ""), qq( ))); $in = 'interactive'->vnew('su'); #require_root_capability(); %options = getVarsFromSh($conffile); $options{MDV_ONLINE} ||= []; # Build the gui add_icon_path('/usr/share/pixmaps/harddrake2/'); $::noborderWhenEmbedded = 1; my $menubar = $ui->get_widget('/MenuBar'); $w->{window}->set_size_request(805, 550) if !$::isEmbedded; my $tree_model = Gtk3::TreeStore->new("Gtk3::Gdk::Pixbuf", "Glib::String", "Glib::Int"); $w->{window}->add(gtkpack_(0, Gtk3::VBox->new(0, 0), 0, $menubar, 0, Gtk3::Banner->new("/usr/share/mcc/themes/default/harddrake-mdk.png", translate("Hardware")), 1, create_hpaned(my $f = gtkadd(Gtk3::Frame->new(N("Detected hardware")), create_scrolled_window(gtkset_size_request(my $tree = Gtk3::TreeView->new_with_model($tree_model), 350, -1), ['automatic', 'automatic'])), gtkpack_(0, Gtk3::VBox->new(0, 0), 1, gtkadd(my $frame = Gtk3::Frame->new(N("Information")), create_scrolled_window(my $text = Gtk3::TextView->new)), 0, my $module_cfg_button = gtksignal_connect(Gtk3::Button->new(N("Set current driver options")), clicked => sub { local $SIG{CHLD} = undef; require modules::interactive; modules::interactive::config_window($in, $current_device); }), 0, my $config_button = gtksignal_connect(Gtk3::Button->new(N("Run config tool")), # we've a configurator, let's add a button for it and show it clicked => sub { return 1 if defined $pid; run_program::raw({ detach => 1 }, $current_configurator); }) ), ), ) ); $f->set_size_request(350, -1); $text->set_wrap_mode('word'); $frame->set_size_request(300, 450) unless $::isEmbedded; # $tree->set_column_auto_resize(0, 1); my (@data, @configurators); $tree->append_column(my $textcolumn = Gtk3::TreeViewColumn->new); $textcolumn->pack_start(my $img_renderer = Gtk3::CellRendererPixbuf->new, 0); $textcolumn->set_attributes($img_renderer, pixbuf => 0); $textcolumn->pack_start(my $text_renderer = Gtk3::CellRendererText->new, 1); $textcolumn->set_attributes($text_renderer, text => 1); $tree->set_headers_visible(0); sub fill_default_text { my ($text) = @_; $text->get_buffer->set_text(N("Click on a device in the tree on the left in order to display its information here.")); } my %has_parameter; $tree->get_selection->signal_connect('changed' => sub { my ($select) = @_; my ($model, $iter) = $select->get_selected; if ($model) { my $idx = $model->get($iter, 2); ($current_device, $current_class) = @{$data[$idx]}; if ($idx ne -1) { my %device_fields = map { # The U+200E character is to force LTR display, as what what follows the colon is always in LTR (device names, paths, etc), # this ensures proper displaying of names like /dev/fd0 (otherwise it gets 'dev/fd0/'). # it must come *after* the space, as the space must follow the colon following the direction of writting. my $field = lookup_field($_); if_($_ && $field->[0], $_ => [ [ $field->[0] . ": \x{200e}", { 'foreground' => 'royalblue3', 'weight' => 'bold' } ], [ ($current_device->{$_} =~ /^(unknown)/ ? N("unknown") : $current_device->{$_} =~ /^(Unknown)/ ? N("Unknown") : $current_device->{$_} eq 'yes' ? N("Yes") : $current_device->{$_} eq 'no' ? N("No") : $current_device->{$_}) . "\n\n", if_($_ eq 'driver' && $current_device->{$_} =~ /^unknown|^Bad:/, { foreground => 'indian red' }) ] ]); } sort keys %$current_device; my %groups = map { if_(ref $groups{$_}, %{$groups{$_}}) } 'generic', $current_class; my ($grouped, $ungrouped) = partition { my $field = $_; member($field, map { @$_ } values %groups); } sort keys %device_fields; my @formated; foreach my $group ($identification, grep { $_ ne $identification } keys %groups) { my @fields = @{$groups{$group}}; # have we at least a member in that group? next unless any { member($_, @fields) } @$grouped; push @formated, titleFormat($group); push @formated, map { if_(ref $_, @$_) } @device_fields{@fields}; } push @formated, if_(@formated && @$ungrouped, titleFormat(N("Misc"))), map { @{$device_fields{$_}} } @$ungrouped; gtktext_insert($text, \@formated); foreach (keys %$current_device) { print qq(Warning: skip "$_" field => "$current_device->{$_}"\n\n) unless (lookup_field($_))[0]; } # if we've valid driver, let's offer to configure it, else hide buttons show_hide(defined($current_device->{driver}) && $current_device->{driver} !~ /^unknown|^Bad|^Card|^Hsf|^kbd|^Mouse:USB|^mouse\d|^Removable:|\|/, $module_cfg_button); $current_configurator = $configurators[$idx]; show_hide($current_configurator && -x first(split /\s+/, $current_configurator), $config_button); # strip arguments for -x test # hide module configuration button if needed: my $driver = $current_device->{driver}; if (!defined $has_parameter{$driver}) { local $SIG{CHLD} = undef; $has_parameter{$driver} ||= to_bool(scalar modules::parameters::parameters($driver)); } show_hide($has_parameter{$driver}, $module_cfg_button); return 1; } } # No device is selected: fill_default_text($text); undef $current_device; $config_button->hide; $module_cfg_button->hide; }); $w->{rwindow}->add_accel_group($ui->get_accel_group); # show the main window earlier (so that sub dialogs can use main # window's icon and so that no Gtk+ critical message got displayed): $w->{rwindow}->show_all; my $flush_guard = Gtk3::GUI_Update_Guard->new; $_->hide foreach $module_cfg_button, $config_button; # hide buttons while no device my $wait = $in->wait_message(N("Please wait"), N("Detection in progress")); gtkflush() if !$::isEmbedded; my $index = 0; my @classes; # Fill the graphic devices tree with a "tree branch" widget per device category foreach my $hw_class (@harddrake::data::tree) { my ($Ident, $title, $icon, $configurator, $detector) = @$hw_class{qw(class string icon configurator detector)}; next if ref($detector) ne "CODE"; #skip class witouth detector next if $Ident =~ /MODEM/ && $::testing; $detect_devices::detect_serial_modem = 0 if $Ident =~ /MODEM/ && !$options{MODEMS_DETECTION}; my @devices = $detector->(\%options); next unless @devices; # Skip empty class (no devices) push @classes, [ $Ident, $title, $icon, $configurator, @devices ]; } # Fill the graphic devices tree with a "tree branch" widget per device category foreach (@classes) { my ($Ident, $title, $icon, $configurator, @devices) = @$_; my $parent_iter = $tree_model->append_set(undef, [ 0 => gtkcreate_pixbuf($icon), 1 => $title, 2 => -1 ]); my $all_hds; $all_hds = fsedit::get_hds() if $Ident eq "HARDDISK"; # Fill the graphic tree with a "tree leaf" widget per device foreach (@devices) { # we really should test for $title there: if ($_->{bus} && $_->{bus} eq "PCI") { # do not display unknown driver for system bridges that're managed by kernel core: delete $_->{driver} if $_->{driver} eq "unknown" && ($Ident =~ /^ATA_STORAGE|BRIDGE|SMB_CONTROLLER|MEMORY_OTHER $/ || $_->{description} =~ /3Com.*5610/); } rename_field($_, 'usb_description', 'description'); # split description into manufacturer/description ($_->{Vendor}, $_->{description}) = split(/\|/, $_->{description}) if $_->{description}; if ($_->{val}) { # Scanner ? my $val = $_->{val}; ($_->{Vendor}, $_->{description}) = split(/\|/, $val->{DESCRIPTION}); } # EIDE detection incoherency: if ($_->{bus} && $_->{bus} eq 'ide') { $_->{channel} = $_->{channel} ? N("secondary") : N("primary"); delete $_->{info} if $_->{Vendor}; } if (defined $_->{capacity}) { my ($burner, $dvd) = (N("burner"), N("DVD")); $_->{capacity} =~ s/burner/$burner/; $_->{capacity} =~ s/DVD/$dvd/; } $configurator .= harddrake::data::set_removable_configurator($Ident, $_); if ($Ident eq "HARDDISK") { my $hd = $_; my $info = find { $_->{device} eq $hd->{device} } @{$all_hds->{hds}}; $hd->{geometry} = join('/', map { $info->{geom}{$_} } qw(cylinders heads sectors)) . " (CHS)"; $hd->{primary_partitions} = @{$info->{primary}{normal}} if $info->{primary}{normal}; $hd->{extended_partitions} = @{$info->{extended}} if $info->{extended}; $hd->{primary_partitions} .= " (" . join(', ', map { $_->{device} }@{$info->{primary}{normal}}) . ")" if $hd->{primary_partitions}; if ($hd->{extended_partitions}) { $hd->{extended_partitions} .= " (" . join(', ', map { $_->{normal}{device} }@{$info->{extended}}) . ")"; } else { delete $hd->{extended_partitions} if $hd->{extended_partitions} eq '0'; } } $_->{EmulateWheel} = bool2yesno($_->{EmulateWheel}) if $Ident eq "MOUSE"; rename_field($_, 'usb_bus', 'bus'); rename_field($_, 'usb_driver', 'driver'); rename_field($_, 'usb_id', 'id'); rename_field($_, 'usb_media_type', 'media_type'); force_rename_field($_, 'nice_media_type', 'media_type'); rename_field($_, 'usb_pci_bus', 'bus'); force_rename_field($_, 'nice_bus', 'bus'); rename_field($_, 'usb_vendor', 'vendor'); rename_field($_, 'vendor_name', 'Vendor'); foreach my $i (qw(synaptics unsafe val wacom)) { delete $_->{$i} } my $custom_id = harddrake::data::custom_id($_, $title); foreach my $field (qw(device)) { $_->{$field} = "/dev/$_->{$field}" if $_->{$field}; } format_bus_ids($_); if ($_->{pci_revision}) { $_->{pci_revision} = sprintf("0x%02x", $_->{pci_revision}); } else { delete $_->{pci_revision};# if $_->{pci_revision} eq "0x0000"; } $tree_model->append_set($parent_iter, [ 1 => $custom_id, 2 => $index++ ]); push @data, [ $_, $Ident ]; push @configurators, $configurator; } } undef $flush_guard; sub format_bus_ids { my ($device) = @_; # do not vivify ids: return if !($device->{vendor} && $device->{id}); foreach my $field (qw(vendor id subvendor subid)) { next if !$device->{$field}; $device->{$field} = sprintf("0x%04x", $device->{$field}); delete $device->{$field} if $device->{$field} eq "0xffff"; # 0xffff equals to '*' } } sub reap_children() { # reap zombies my $child_pid; do { $child_pid = waitpid(-1, POSIX::WNOHANG); undef $pid if $pid == $child_pid } until $child_pid > 0; } $SIG{CHLD} = \&reap_children; $w->{rwindow}->signal_connect(delete_event => \&quit_global); $w->{rwindow}->set_position('center') unless $::isEmbedded; foreach (keys %menu_options) { $options{$_} = 0 unless defined($options{$_}); # force detection by default $check_boxes{$_} = $ui->get_widget('/MenuBar/OptionsMenu/' . $_); $check_boxes{$_}->set_active($options{$_}); # restore saved values } $textcolumn->set_min_width(350); #$textcolumn->set_minmax_width(400); $textcolumn->set_sizing('GTK_TREE_VIEW_COLUMN_AUTOSIZE');#GROW_ONLY #$tree->columns_autosize(); $tree->signal_connect(realize => sub { $tree->get_selection->select_path(Gtk3::TreePath->new_first) }); { $SIG{CHLD} = undef; #local $SIG{CHLD} = sub {}; if (my @packages = difference2([ pkgs::detect_hardware_packages($in->do_pkgs) ], [ qw(ati.2 dmraid gnome-alsamixer mdadm xmms-alsa) ])) { @packages = difference2(\@packages, [ $in->do_pkgs->are_installed(@packages) ]); # we use uniq() because on biarch machines, we got packages twice: my @packages2install = uniq($in->do_pkgs->are_available(@packages)); undef $wait; if (@packages2install && $in->ask_yesorno(N("Warning"), N("The following packages need to be installed:\n") . join(', ', @packages2install))) { $in->do_pkgs->install(@packages2install); } } } $SIG{CHLD} = \&reap_children; undef $wait; # fill in default right text since no device is selected on startup: fill_default_text($text); $w->main; sub about() { my $license = formatAlaTeX(translate($::license)); $license =~ s/\n/\n\n/sg; # nicer formatting my $w = gtknew('AboutDialog', name => N("Harddrake"), version => mageia_release_info()->{version}, logo => '/usr/share/icons/harddrake.png', copyright => N("Copyright (C) %s by %s", '2001-2008', 'Mandriva') . "\n" . N("Copyright (C) %s by %s", '2011', N("Mageia")), license => $license, wrap_license => 1, comments => N("This is HardDrake, a %s hardware configuration tool.", $distro_name), website => 'http://www.mageia.org', website_label => N("Mageia"), authors => [ 'Thierry Vignaud ' ], translator_credits => #-PO: put here name(s) and email(s) of translator(s) (eg: "John Smith ") N("_: Translator(s) name(s) & email(s)\n"), transient_for => $::main_window, modal => 1, position_policy => 'center-on-parent', ); $w->show_all; $w->run; } sub fields_help() { if ($current_device) { create_dialog(N("Harddrake help"), '' . N("Description of the fields:\n\n") . '' . join("\n\n", map { my $info = lookup_field($_); if_($info->[0], formatAlaTeX(qq($info->[0]: $info->[1]))); } sort keys %$current_device), { use_markup => 1, transient => $w->{real_window}, height => 400, scroll => 1 }); } else { create_dialog(N("Select a device!"), N("Once you've selected a device, you'll be able to see the device information in fields displayed on the right frame (\"Information\")"), { transient => $w->{real_window} }); } } sub quit_global() { kill(15, $pid) if $pid; setVarsInSh($conffile, \%options) if !$::testing; ugtk3->exit(0); } sub run_drakbug() { run_program::raw({ detach => 1 }, 'drakbug', '--report', 'harddrake'); } sub run_help() { run_program::raw({ detach => 1 }, 'drakhelp', '--id', 'harddrake'); } sub handle_modem_option() { $options{MODEMS_DETECTION} = $check_boxes{MODEMS_DETECTION}->get_active; } sub handle_zip_option() { $options{PARALLEL_ZIP_DETECTION} = $check_boxes{PARALLEL_ZIP_DETECTION}->get_active; } sub show_hide { my ($bool, $button) = @_; if ($bool) { $button->show } else { $button->hide } } sub lookup_field { my ($field) = @_; my $class = find { defined $fields{$_} && defined $fields{$_}{$field} } ($current_class, 'generic'); $fields{$class}{$field}; } sub titleFormat { my ($title) = @_; [ $title . "\n", { 'weight' => 'bold', scale => 1.4399999999999 } ]; # Pango->PANGO_SCALE_LARGE } sub force_rename_field { my ($dev, $new_field, $field) = @_; if ($dev->{$new_field}) { delete $dev->{$field}; rename_field($dev, $new_field, $field); } } sub rename_field { my ($dev, $field, $new_field) = @_; if ($dev->{$field}) { if ($dev->{$new_field}) { $dev->{$new_field} .= " ($dev->{$field})"; } else { $dev->{$new_field} = $dev->{$field}; } delete $dev->{$field}; } } sub popup_menu { my ($menu) = @_; sub { my (undef, $event) = @_; if ($event->type eq 'button-press') { $menu->popup(undef, undef, undef, undef, $event->button, $event->time); # Tell calling code that we have handled this event; the buck stops here. return 1; } # Tell calling code that we have not handled this event; pass it on. return 0; }; } a id='n679' href='#n679'>679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
package install::steps_interactive; # $Id$


use strict;

our @ISA = qw(install::steps);


#-######################################################################################
#- misc imports
#-######################################################################################
use common;
use partition_table;
use fs::type;
use fs::partitioning;
use fs::partitioning_wizard;
use install::steps;
use install::interactive;
use install::any;
use messages;
use detect_devices;
use run_program;
use devices;
use fsedit;
use mouse;
use modules;
use modules::interactive;
use lang;
use keyboard;
use any;
use log;

#-######################################################################################
#- In/Out Steps Functions
#-######################################################################################
sub errorInStep {
    my ($o, $err) = @_;
    $o->ask_warn(N("Error"), [ N("An error occurred"), formatError($err) ]);
}

sub kill_action {
    my ($o) = @_;
    $o->kill;
}

#-######################################################################################
#- Steps Functions
#-######################################################################################
#------------------------------------------------------------------------------

sub acceptLicense {
    my ($o) = @_;
    any::acceptLicense($o);
}

sub selectLanguage {
    my ($o) = @_;

    any::selectLanguage_install($o, $o->{locale});
    install::steps::selectLanguage($o);

    if ($o->isa('interactive::gtk')) {
	$o->ask_warn('', formatAlaTeX(
"If you see this message it is because you chose a language for
which DrakX does not include a translation yet; however the fact
that it is listed means there is some support for it anyway.

That is, once GNU/Linux will be installed, you will be able to at
least read and write in that language; and possibly more (various
fonts, spell checkers, various programs translated etc. that
varies from language to language).")) if $o->{locale}{lang} !~ /^en/ && !lang::load_mo();
    } else {
	#- no need to have this in po since it is never translated
	$o->ask_warn('', "The characters of your language can not be displayed in console,
so the messages will be displayed in english during installation") if $ENV{LANGUAGE} eq 'C';
    }
}

#------------------------------------------------------------------------------
sub selectKeyboard {
    my ($o, $clicked) = @_;

    my $from_usb = keyboard::from_usb();
    my $l = keyboard::lang2keyboards(lang::langs($o->{locale}{langs}));

    if ($clicked || !($from_usb || @$l && $l->[0][1] >= 90) || listlength(lang::langs($o->{locale}{langs})) > 1) {
	add2hash($o->{keyboard}, $from_usb);
	my @best = uniq($from_usb ? $from_usb->{KEYBOARD} : (), map { $_->[0] } @$l);
	@best = () if @best == 1;

	my $format = sub { translate(keyboard::KEYBOARD2text($_[0])) };
	my $other;
	my $ext_keyboard = my $KEYBOARD = $o->{keyboard}{KEYBOARD};
	$o->ask_from_(
		      { title => N("Keyboard"), 
			icon => 'banner-languages',
			messages => N("Please choose your keyboard layout."),
			interactive_help_id => 'selectKeyboard',
			advanced_messages => N("Here is the full list of available keyboards"),
			advanced_label => N("More"),
		      },
		      [ if_(@best, { val => \$KEYBOARD, type => 'list', format => $format, sort => 1,
				     list => [ @best ], changed => sub { $other = 0 } }),
			{ val => \$ext_keyboard, type => 'list', format => $format, changed => sub { $other = 1 },
			  list => [ difference2([ keyboard::KEYBOARDs() ], \@best) ], advanced => @best > 1 }
		      ]);
	$o->{keyboard}{KEYBOARD} = !@best || $other ? $ext_keyboard : $KEYBOARD;
	delete $o->{keyboard}{unsafe};
    }
    keyboard::group_toggle_choose($o, $o->{keyboard}) or goto &selectKeyboard;
    install::steps::selectKeyboard($o);
}

#------------------------------------------------------------------------------
sub selectInstallClass {
    my ($o) = @_;

    if (my @l = install::any::find_root_parts($o->{fstab}, $::prefix)) {
	log::l("proposing to upgrade partitions " . join(" ", map { $_->{part} && $_->{part}{device} } @l));

	my @releases = uniq(map { $_->{release} } @l);
	if (@releases != @l) {
	    #- same release name so adding the device to differentiate them:
	    $_->{release} .= " ($_->{part}{device})" foreach @l;
	}

	my $p;
	$o->ask_from_({ title => N("Install/Upgrade"),
			messages => N("Is this an install or an upgrade?"),
			interactive_help_id => 'selectInstallClass',
		      },
		      [ { val => \$p,
			  list => [ @l, N_("Install") ], 
			  type => 'list',
			  format => sub { ref($_[0]) ? N("Upgrade %s", $_[0]{release}) : translate($_[0]) }
			} ]);
	if (ref $p) {
	    if ($p->{part}) {
		log::l("choosing to upgrade partition $p->{part}{device}");
		$o->{migrate_device_names} = install::any::use_root_part($o->{all_hds}, $p->{part}, $o);
	    }

	    #- handle encrypted partitions (esp. /home)
	    foreach (grep { $_->{mntpoint} } @{$o->{fstab}}) {
		my ($options, $_unknown) = fs::mount_options::unpack($_);
		$options->{encrypted} or next;
		$o->ask_from_({ focus_first => 1 },
			      [ { label => N("Encryption key for %s", $_->{mntpoint}),
				  hidden => 1, val => \$_->{encrypt_key} } ]);
	    }

	    $o->{isUpgrade} = (find { $p->{release_file} =~ /$_/ } 'mandriva', 'mandrake', 'conectiva', 'redhat') || 'unknown';
	    $o->{upgrade_by_removing_pkgs_matching} ||= {
		conectiva => 'cl',
		redhat => '.', #- everything!
	    }->{$o->{isUpgrade}};
	    log::l("upgrading $o->{isUpgrade} distribution" . ($o->{upgrade_by_removing_pkgs_matching} ? " (upgrade_by_removing_pkgs_matching $o->{upgrade_by_removing_pkgs_matching})" : ''));
	}
    }
}

#------------------------------------------------------------------------------
sub selectMouse {
    my ($o, $force) = @_;

    $force || $o->{mouse}{unsafe} or return;

    mouse::select($o, $o->{mouse}) or return;
   
    if ($o->{mouse}{device} eq "input/mice") {
	modules::interactive::load_category($o, $o->{modules_conf}, 'bus/usb', 1, 0);
	eval { 
	    devices::make("usbmouse");
	    modules::load(qw(usbhid mousedev usbmouse));
	};
    }
}
#------------------------------------------------------------------------------
sub setupSCSI {
    my ($o) = @_;

    install::any::configure_pcmcia($o);
    { 
	my $_w = $o->wait_message(N("IDE"), N("Configuring IDE"));
	modules::load(modules::category2modules('disk/cdrom'));
    }
    modules::interactive::load_category($o, $o->{modules_conf}, 'bus/firewire', 1);

    my $have_non_scsi = detect_devices::hds(); #- at_least_one scsi device if we have no disks
    modules::interactive::load_category($o, $o->{modules_conf}, 'disk/card_reader|ide|scsi|hardware_raid|sata|firewire', 1, !$have_non_scsi);
    modules::interactive::load_category($o, $o->{modules_conf}, 'disk/card_reader|ide|scsi|hardware_raid|sata|firewire') if !detect_devices::hds(); #- we really want a disk!

    install::interactive::tellAboutProprietaryModules($o);

    install::any::getHds($o, $o);
}

#------------------------------------------------------------------------------
sub doPartitionDisks {
    my ($o) = @_;

    if (arch() =~ /ppc/) {
	my $generation = detect_devices::get_mac_generation();
	if ($generation =~ /NewWorld/) {
	    #- mac partition table
	    if (defined $partition_table::mac::bootstrap_part) {
    		#- do not do anything if we've got the bootstrap setup
    		#- otherwise, go ahead and create one somewhere in the drive free space
	    } else {
		my $freepart = $partition_table::mac::freepart;
		if ($freepart && $freepart->{size} >= 1) {
		    log::l("creating bootstrap partition on drive /dev/$freepart->{hd}{device}, block $freepart->{start}");
		    $partition_table::mac::bootstrap_part = $freepart->{part};
		    log::l("bootstrap now at $partition_table::mac::bootstrap_part");
		    my $p = { start => $freepart->{start}, size => 1 << 11, mntpoint => '' };
		    fs::type::set_pt_type($p, 0x401);
		    fsedit::add($freepart->{hd}, $p, $o->{all_hds}, { force => 1, primaryOrExtended => 'Primary' });
		    $partition_table::mac::new_bootstrap = 1;

    		} else {
		    $o->ask_warn('', N("No free space for 1MB bootstrap! Install will continue, but to boot your system, you'll need to create the bootstrap partition in DiskDrake"));
    		}
	    }
	} elsif ($generation =~ /IBM/) {
	    #- dos partition table
	    $o->ask_warn('', N("You'll need to create a PPC PReP Boot bootstrap! Install will continue, but to boot your system, you'll need to create the bootstrap partition in DiskDrake"));
	}
    }

    if (!$o->{isUpgrade}) {
        fs::partitioning_wizard::main($o, $o->{all_hds}, $o->{fstab}, $o->{manualFstab}, $o->{partitions}, $o->{partitioning}, $::local_install);
    }
}

#------------------------------------------------------------------------------
sub rebootNeeded {
    my ($o) = @_;
    fs::partitioning_wizard::warn_reboot_needed($o);
    install::steps::rebootNeeded($o);
}

#------------------------------------------------------------------------------
sub choosePartitionsToFormat {
    my ($o) = @_;
    fs::partitioning::choose_partitions_to_format($o, $o->{fstab});
}

sub formatMountPartitions {
    my ($o, $_fstab) = @_;
    fs::partitioning::format_mount_partitions($o, $o->{all_hds}, $o->{fstab});
}

#------------------------------------------------------------------------------
sub setPackages {
    my ($o) = @_;
    install::any::setPackages($o);
}

#- group by CD
sub ask_deselect_media__copy_on_disk {
    my ($o, $hdlists, $o_copy_rpms_on_disk) = @_;

    log::l("ask_deselect_media__copy_on_disk");

    my @names = uniq(map { $_->{name} } @$hdlists);
    my %selection = map { $_ => 1 } @names;

    $o->ask_from_({ messages => formatAlaTeX(N("The following installation media have been found.
If you want to skip some of them, you can unselect them now.")) },
		[ (map { { type => 'bool', text => $_, val => \$selection{$_}, 
			    if_($_ eq $names[0], disabled => sub { 1 }),
			} } @names),
		  if_($o_copy_rpms_on_disk,
		    { type => 'label', val => \(formatAlaTeX(N("You have the option to copy the contents of the CDs onto the hard drive before installation.
It will then continue from the hard drive and the packages will remain available once the system is fully installed."))) },
		    { type => 'bool', text => N("Copy whole CDs"), val => $o_copy_rpms_on_disk },
		  ),
		]);
    $_->{selected} = $selection{$_->{name}} foreach @$hdlists;
    log::l("keeping media " . join ',', map { $_->{rpmsdir} } grep { $_->{selected} } @$hdlists);
}

sub while_suspending_time {
    my ($o, $f) = @_;

    my $time = time();

    my $r = $f->();

    #- add the elapsed time (otherwise the predicted time will be rubbish)
    $o->{install_start_time} += time() - $time;

    $r;
}

# nb: $file can be a directory
sub ask_change_cd {
    my ($o, $phys_m, $o_rel_file) = @_;

    while_suspending_time($o, sub { ask_change_cd_($o, $phys_m, $o_rel_file) });
}

sub ask_change_cd_ {
    my ($o, $phys_m, $o_rel_file) = @_;

    local $| = 1; print "\a";

    if ($phys_m->{name} =~ /commercial/i) {
	$o->{useless_thing_accepted2} ||= 
	  $o->ask_from_list_('', formatAlaTeX(messages::com_license()), 
			     [ N_("Accept"), N_("Refuse") ], "Accept") eq "Accept" or return;
    }

    foreach (1 .. 32) {
	install::media::umount_phys_medium($phys_m);
	install::media::openCdromTray($phys_m->{device});

	$o->ask_okcancel('', N("Change your Cd-Rom!
Please insert the Cd-Rom labelled \"%s\" in your drive and press Ok when done.
If you do not have it, press Cancel to avoid installation from this Cd-Rom.", $phys_m->{name}), 1) or return;

	fs::mount::part($phys_m);

	#- it can be a directory, so don't use -f
	!$o_rel_file || -e install::media::path($phys_m, $o_rel_file) and return 1;

	log::l("file " . install::media::path($phys_m, $o_rel_file) . " not found");
    }
    undef;
}

sub selectSupplMedia {
    my ($o) = @_;
    install::any::selectSupplMedia($o);
}
#------------------------------------------------------------------------------
sub choosePackages {
    my ($o) = @_;

    my $w = $o->wait_message('', N("Looking for available packages..."));
    my $availableC = &install::steps::choosePackages;
    my $individual;

    require install::pkgs;

    my $min_size = install::pkgs::selectedSize($o->{packages});
    undef $w;
    if ($min_size >= $availableC) {
	my $msg = N("Your system does not have enough space left for installation or upgrade (%dMB > %dMB)",
		    $min_size / sqr(1024), $availableC / sqr(1024));
	log::l($msg);
	$o->ask_warn('', $msg);
	install::steps::rebootNeeded($o);
    }

    my $min_mark = 4;

  chooseGroups:
    $o->chooseGroups($o->{packages}, $o->{compssUsers}, $min_mark, \$individual) if !$o->{isUpgrade} && $o->{meta_class} ne 'desktop';

    ($o->{packages_}{ind}) =
      install::pkgs::setSelectedFromCompssList($o->{packages}, $o->{rpmsrate_flags_chosen}, $min_mark, $availableC);

    $o->choosePackagesTree($o->{packages}) or goto chooseGroups if $individual;

    install::any::warnAboutRemovedPackages($o, $o->{packages});
    install::any::warnAboutNaughtyServers($o) or goto chooseGroups if !$o->{isUpgrade} && $o->{meta_class} ne 'firewall';
}

sub choosePackagesTree {
    my ($o, $packages, $o_limit_to_medium) = @_;

    $o->ask_many_from_list('', N("Choose the packages you want to install"),
			   {
			    list => [ grep { !$o_limit_to_medium || install::pkgs::packageMedium($packages, $_) == $o_limit_to_medium }
				      @{$packages->{depslist}} ],
			    value => \&URPM::Package::flag_selected,
			    label => \&URPM::Package::name,
			    sort => 1,
			   });
}
sub loadSavePackagesOnFloppy {
    my ($o, $packages) = @_;
    $o->ask_from('', 
N("Please choose load or save package selection.
The format is the same as auto_install generated files."),
		 [ { val => \ (my $choice), list => [ N_("Load"), N_("Save") ], format => \&translate, type => 'list' } ]) or return;

    if ($choice eq 'Load') {
	while (1) {
	    log::l("load package selection");
	    my ($_h, $fh) = install::any::media_browser($o, '', 'package_list.pl') or return;
	    my $O = eval { install::any::loadO(undef, $fh) };
	    if ($@) {
		$o->ask_okcancel('', N("Bad file")) or return;
	    } else {
		install::any::unselectMostPackages($o);
		install::pkgs::select_by_package_names($packages, $O->{default_packages} || []);
		return 1;
	    }
	}
    } else {
	log::l("save package selection");
	install::any::g_default_packages($o);
    }
}
sub chooseGroups {
    my ($o, $packages, $compssUsers, $min_level, $individual) = @_;

    #- for all groups available, determine package which belongs to each one.
    #- this will enable getting the size of each groups more quickly due to
    #- limitation of current implementation.
    #- use an empty state for each one (no flag update should be propagated).
    
    my $b = install::pkgs::saveSelected($packages);
    install::any::unselectMostPackages($o);
    install::pkgs::setSelectedFromCompssList($packages, { CAT_SYSTEM => 1 }, $min_level, 0);
    my $system_size = install::pkgs::selectedSize($packages);
    my ($sizes, $pkgs) = install::pkgs::computeGroupSize($packages, $min_level);
    install::pkgs::restoreSelected($b);
    log::l("system_size: $system_size");

    my %stable_flags = grep_each { $::b } %{$o->{rpmsrate_flags_chosen}};
    delete $stable_flags{"CAT_$_"} foreach map { @{$_->{flags}} } @{$o->{compssUsers}};

    my $compute_size = sub {
	my %pkgs;
	my %flags = %stable_flags; @flags{@_} = ();
	my $total_size;
	A: while (my ($k, $size) = each %$sizes) {
	    Or: foreach (split "\t", $k) {
		  foreach (split "&&") {
		      exists $flags{$_} or next Or;
		  }
		  $total_size += $size;
		  $pkgs{$_} = 1 foreach @{$pkgs->{$k}};
		  next A;
	      }
	  }
	log::l("computed size $total_size (flags " . join(' ', keys %flags) . ")");
	log::l("chooseGroups: ", join(" ", sort keys %pkgs));

	int $total_size;
    };

    my ($size, $unselect_all);
    my $available_size = install::any::getAvailableSpace($o) / sqr(1024);
    my $size_to_display = sub { 
	my $lsize = $system_size + $compute_size->(map { "CAT_$_" } map { @{$_->{flags}} } grep { $_->{selected} } @$compssUsers);

	#- if a profile is deselected, deselect everything (easier than deselecting the profile packages)
	$unselect_all ||= $size > $lsize;
	$size = $lsize;
	N("Total size: %d / %d MB", install::pkgs::correctSize($size / sqr(1024)), $available_size);
    };

    while (1) {
	if ($available_size < 200) {
	    # too small to choose anything. Defaulting to no group chosen
	    $_->{selected} = 0 foreach @$compssUsers;
	    last;
	}

	$o->reallyChooseGroups($size_to_display, $individual, $compssUsers) or return;

	last if $::testing || install::pkgs::correctSize($size / sqr(1024)) < $available_size || every { !$_->{selected} } @$compssUsers;
       
	$o->ask_warn('', N("Selected size is larger than available space"));	
    }
    install::any::set_rpmsrate_category_flags($o, $compssUsers);

    log::l("compssUsersChoice selected: ", join(', ', map { qq("$_->{path}|$_->{label}") } grep { $_->{selected} } @$compssUsers));

    #- do not try to deselect package (by default no groups are selected).
    if (!$o->{isUpgrade}) {
	install::any::unselectMostPackages($o) if $unselect_all;
    }
    #- if no group have been chosen, ask for using base system only, or no X, or normal.
    if (!$o->{isUpgrade} && !any { $_->{selected} } @$compssUsers) {
	my $docs = !$o->{excludedocs};	
	my $minimal;

	$o->ask_from(N("Type of install"), 
		     N("You have not selected any group of packages.
Please choose the minimal installation you want:"),
		     [
		      { val => \$o->{rpmsrate_flags_chosen}{CAT_X}, type => 'bool', text => N("With X"), disabled => sub { $minimal } },
		      { val => \$docs, type => 'bool', text => N("With basic documentation (recommended!)"), disabled => sub { $minimal } },
		      { val => \$minimal, type => 'bool', text => N("Truly minimal install (especially no urpmi)") },
		     ],
	) or return &chooseGroups;

	if ($minimal) {
	    $o->{rpmsrate_flags_chosen}{CAT_X} = $docs = 0;
	    $o->{rpmsrate_flags_chosen}{CAT_SYSTEM} = 0;
	}
	$o->{excludedocs} = !$docs;
	$o->{rpmsrate_flags_chosen}{CAT_MINIMAL_DOCS} = $docs;

	install::any::unselectMostPackages($o);
    }
    1;
}

sub reallyChooseGroups {
    my ($o, $size_to_display, $individual, $compssUsers) = @_;

    my $size_text = &$size_to_display;

    my ($path, $all);
    $o->ask_from_({ messages => N("Package Group Selection"),
		    interactive_help_id => 'choosePackages',
		  }, [
        { val => \$size_text, type => 'label' }, {},
	 (map { 
	       my $old = $path;
	       $path = $_->{path};
	       if_($old ne $path, { val => translate($path) }),
		 {
		  val => \$_->{selected},
		  type => 'bool',
		  disabled => sub { $all },
		  text => translate($_->{label}),
		  help => translate($_->{descr}),
		  changed => sub { $size_text = &$size_to_display },
		 };
	   } @$compssUsers),
	 if_($o->{meta_class} eq 'desktop', { text => N("All"), val => \$all, type => 'bool' }),
	 if_($individual, { text => N("Individual package selection"), val => $individual, advanced => 1, type => 'bool' }),
    ]);

    if ($all) {
	$_->{selected} = 1 foreach @$compssUsers;
    }
    1;    
}

#------------------------------------------------------------------------------
sub installPackages {
    my ($o, $packages) = @_;
    my ($current, $total) = (0, 0);

    my ($_w, $wait_message) = $o->wait_message_with_progress_bar(N("Installing"));
    $wait_message->(N("Preparing installation"), 0, 100); #- beware, interactive::curses::wait_message_with_progress_bar need to create the Dialog::Progress here because in installCallback we are chrooted

    local *install::steps::installCallback = sub {
	my ($packages, $type, $id, $subtype, $_amount, $total_) = @_;
	if ($type eq 'user' && $subtype eq 'install') {
	    $total = $total_;
	} elsif ($type eq 'inst' && $subtype eq 'start') {
	    my $p = $packages->{depslist}[$id];
	    $wait_message->(N("Installing package %s", $p->name), $current, $total);
	    $current += $p->size;
	}
    };

    my $install_result;
    catch_cdie { $install_result = $o->install::steps::installPackages($packages) }
      sub { installPackages__handle_error($o, $_[0]) };

    if ($install::pkgs::cancel_install) {
	$install::pkgs::cancel_install = 0;
	die "setstep choosePackages\n";
    }
    $install_result;
}

sub installPackages__handle_error {
    my ($o, $err_ref) = @_;

    log::l("catch_cdie: $$err_ref");
    my $time = time();
    my $go_on;
    if ($$err_ref =~ /^error ordering package list: (.*)/) {
	$go_on = $o->ask_yesorno('', [
	    N("There was an error ordering packages:"), $1, N("Go on anyway?") ], 1);
    } elsif ($$err_ref =~ /^error installing package list: (\S+)\s*(.*)/) {
	my ($pkg_name, $medium_name) = ($1, $2);
	my @choices = (
	    [ 'retry', N("Retry") ],
	    [ 'skip_one', N("Skip this package") ],
	    [ 'disable_media', N("Skip all packages from medium \"%s\"", $medium_name) ],
	    [ '', N("Go back to media and packages selection") ],
	);
	my $choice;
	$o->ask_from_({ messages => N("There was an error installing package %s.", $pkg_name) },
		      [ { val => \$choice, type => 'list', list => \@choices, format => sub { $_[0][1] } } ]);
	$go_on = $choice->[0];
    }
    if ($go_on) {
	#- add the elapsed time (otherwise the predicted time will be rubbish)
	$o->{install_start_time} += time() - $time;
	$go_on;
    } else {
	$o->{askmedia} = 1;
	$$err_ref = "already displayed";
	0;
    }
}


sub afterInstallPackages($) {
    my ($o) = @_;
    my $_w = $o->wait_message('', N("Post-install configuration"));
    $o->SUPER::afterInstallPackages;
}

sub updatemodules {
    my ($o, $dev, $rel_dir) = @_;

    $o->ask_okcancel('', N("Please ensure the Update Modules media is in drive %s", $dev), 1) or return;
    $o->SUPER::updatemodules($dev, $rel_dir);
}

#------------------------------------------------------------------------------
sub configureNetwork {
    my ($o) = @_;
    if ($o->{meta_class} eq 'firewall') {
	require network::netconnect;
	network::netconnect::real_main($o->{net}, $o, $o->{modules_conf});
    } else {
	#- don't overwrite configuration in a network install
	if (!install::any::is_network_install($o)) {
	    require network::network;
	    network::network::easy_dhcp($o->{net}, $o->{modules_conf});
	}
	$o->SUPER::configureNetwork;
    }
}

#------------------------------------------------------------------------------
sub installUpdates {
    my ($o) = @_;
    my $u = $o->{updates} ||= {};
    
    $o->hasNetwork or return;

    $o->ask_yesorno_({ title => N("Updates"), icon => 'banner-update', messages => formatAlaTeX(
N("You now have the opportunity to download updated packages. These packages
have been updated after the distribution was released. They may
contain security or bug fixes.

To download these packages, you will need to have a working Internet 
connection.

Do you want to install the updates?")),
			   interactive_help_id => 'installUpdates',
					       }) or return;

    #- bring all interface up for installing updates packages.
    install::interactive::upNetwork($o);

    #- update medium available and working.
    my $update_medium;
    do {
	$u->{url} = install::any::ask_mirror($o, 'updates', $u->{url}) or goto &installUpdates;
	my $phys_medium = install::media::url2mounted_phys_medium($o, $u->{url} . '/media/main/updates');

	eval {
	    my $_w = $o->wait_message('', N("Contacting the mirror to get the list of available packages..."));
	    $update_medium = { name => "Updates for Mandriva Linux " . $o->{product_id}{version}, update => 1 };
	    install::media::get_standalone_medium($o, $phys_medium, $o->{packages}, $update_medium);
	};
	if ($@) {
	    undef $update_medium;
	    $o->ask_warn('', N("Unable to contact mirror %s", $u->{mirror}));
	}
    } until $update_medium;

    if ($update_medium) {
	if ($o->choosePackagesTree($o->{packages}, $update_medium)) {
	    $o->{isUpgrade} = 1; #- now force upgrade mode, else update will be installed instead of upgraded.
	    $o->pkg_install;
	} else {
	    #- make sure to not try to install the packages (which are automatically selected by getPackage above).
	    #- this is possible by deselecting the medium (which can be re-selected above).
	    $update_medium->{selected} = 0;
	    goto &installUpdates;
	}
	#- update urpmi even, because there is an hdlist available and everything is good,
	#- this will allow user to update the medium but update his machine later.
	install::steps::install_urpmi($o);
    }

    #- not downing network, even ppp. We don't care much since it is the end of install :)
}


#------------------------------------------------------------------------------
sub configureTimezone {
    my ($o, $clicked) = @_;

    any::configure_timezone($o, $o->{timezone}, $clicked) or return;

    install::steps::configureTimezone($o);
    1;
}

#------------------------------------------------------------------------------
sub configureServices { 
    my ($o, $clicked) = @_;
    require services;
    $o->{services} = services::ask($o) if $clicked;
    install::steps::configureServices($o);
}


sub summaryBefore {
    my ($o) = @_;

    install::any::preConfigureTimezone($o);
    #- get back network configuration.
    require network::network;
    eval {
	network::network::read_net_conf($o->{net});
    };
    log::l("summaryBefore: network configuration: ", formatError($@)) if $@;
}

sub summary_prompt {
    my ($o, $l, $check_complete) = @_;

    foreach (@$l) {
	my $val = $_->{val};
	($_->{format}, $_->{val}) = (sub { $val->() || N("not configured") }, '');
    }
    
    $o->ask_from_({
		   messages => N("Summary"),
		   interactive_help_id => 'summary',
		   cancel   => '',
		   callbacks => { complete => sub { !$check_complete->() } },
		  }, $l);
}

sub summary {
    my ($o) = @_;

    my @l;

    push @l, {
	group => N("System"), 
	label => N("Keyboard"), 
	val => sub { $o->{keyboard} && translate(keyboard::keyboard2text($o->{keyboard})) },
	clicked => sub { $o->selectKeyboard(1) },
    };

    my $timezone_manually_set;  
    push @l, {
	group => N("System"),
	label => N("Country / Region"),
	val => sub { lang::c2name($o->{locale}{country}) },
	clicked => sub {
	    any::selectCountry($o, $o->{locale}) or return;

	    my $pkg_locale = lang::locale_to_main_locale(lang::getlocale_for_country($o->{locale}{lang}, $o->{locale}{country}));
	    my @pkgs = install::pkgs::packagesProviding($o->{packages}, "locales-$pkg_locale");
	    $o->pkg_install(map { $_->name } @pkgs) if @pkgs;

	    lang::write_and_install($o->{locale}, $o->do_pkgs);
	    if (!$timezone_manually_set) {
		delete $o->{timezone};
		install::any::preConfigureTimezone($o); #- now we can precise the timezone thanks to the country
	    }
	},
    };
    push @l, {
	group => N("System"),
	label => N("Timezone"),
	val => sub { $o->{timezone}{timezone} },
	clicked => sub { $timezone_manually_set = $o->configureTimezone(1) || $timezone_manually_set },
    };

    push @l, {
	group => N("System"),
	label => N("Mouse"),
	val => sub { translate($o->{mouse}{type}) . ' ' . translate($o->{mouse}{name}) },
	clicked => sub { selectMouse($o, 1); mouse::write($o->do_pkgs, $o->{mouse}) },
    };


    my @sound_cards = detect_devices::getSoundDevices();

    my $sound_index = 0;
    foreach my $device (@sound_cards) {
	$device->{sound_slot_index} = $sound_index;
	push @l, {
	    group => N("Hardware"),
	    label => N("Sound card"),
	    val => sub { 
		$device->{driver} && modules::module2description($device->{driver}) || $device->{description};
	    },
	    clicked => sub {
	        require harddrake::sound; 
	        harddrake::sound::config($o, $o->{modules_conf}, $device);
	    },
	};
     $sound_index++;
    }

    if (!@sound_cards && ($o->{rpmsrate_flags_chosen}{CAT_GAMES} || $o->{rpmsrate_flags_chosen}{CAT_AUDIO})) {
	#- if no sound card are detected AND the user selected things needing a sound card,
	#- propose a special case for ISA cards
	push @l, {
	    group => N("Hardware"),
	    label => N("Sound card"),
	    val => sub {},
	    clicked => sub {
	        if ($o->ask_yesorno('', N("Do you have an ISA sound card?"))) {
	    	  $o->do_pkgs->install(qw(alsa-utils sndconfig aoss));
	    	  $o->ask_warn('', N("Run \"alsaconf\" or \"sndconfig\" after installation to configure your sound card"));
	        } else {
	    	  $o->ask_warn('', N("No sound card detected. Try \"harddrake\" after installation"));
	        }
	    },
	};
    }

    foreach my $tv (detect_devices::getTVcards()) {
	push @l, {
	    group => N("Hardware"),
	    label => N("TV card"),
	    val => sub { $tv->{description} }, 
	    clicked => sub { 
	        require harddrake::v4l; 
	        harddrake::v4l::config($o, $o->{modules_conf}, $tv->{driver});
	    }
	};
    }

    push @l, {
	group => N("Hardware"),
	label => N("Graphical interface"),
	val => sub { $o->{raw_X} ? Xconfig::various::to_string($o->{raw_X}) : '' },
	clicked => sub { configureX($o, 'expert') }, 
    };

    push @l, {
	group => N("Network & Internet"),
	label => N("Network"),
	val => sub { $o->{net}{type} },
	clicked => sub { 
	    require network::netconnect;
	    network::netconnect::real_main($o->{net}, $o, $o->{modules_conf});
	},
    };

    $o->{miscellaneous} ||= {};
    push @l, {
	group => N("Network & Internet"),
	label => N("Proxies"),
	val => sub { $o->{miscellaneous}{http_proxy} || $o->{miscellaneous}{ftp_proxy} ? N("configured") : N("not configured") },
	clicked => sub { 
	    require network::network;
	    network::network::miscellaneous_choose($o, $o->{miscellaneous});
	    network::network::proxy_configure($o->{miscellaneous}) if !$::testing;
	},
    };

    push @l, {
	group => N("Security"),
	label => N("Security Level"),
	val => sub { 
	    require security::level;
	    security::level::to_string($o->{security});
	},
	clicked => sub {
	    require security::level;
	    security::level::level_choose($o, \$o->{security}, \$o->{libsafe}, \$o->{security_user})
		and install::any::set_security($o);
	},
    };

    push @l, {
	group => N("Security"),
	label => N("Firewall"),
	val => sub { 
	    require network::shorewall;
	    my $shorewall = network::shorewall::read();
	    $shorewall && !$shorewall->{disabled} ? N("activated") : N("disabled");
	},
	clicked => sub { 
	    require network::drakfirewall;
	    if (my @rc = network::drakfirewall::main($o, $o->{security} <= 3)) {
		$o->{firewall_ports} = !$rc[0] && $rc[1];
	    }
	},
    } if detect_devices::get_net_interfaces();

    push @l, {
	group => N("Boot"),
	label => N("Bootloader"),
	val => sub { 
	    #-PO: example: lilo-graphic on /dev/hda1
	    N("%s on %s", $o->{bootloader}{method}, $o->{bootloader}{boot});
	},
	clicked => sub { 
	    any::setupBootloader($o, $o->{bootloader}, $o->{all_hds}, $o->{fstab}, $o->{security}) or return;
	    any::installBootloader($o, $o->{bootloader}, $o->{all_hds});
	},
    };

    push @l, {
	group => N("System"),
	label => N("Services"),
	val => sub {
	    require services;
	    my ($l, $activated) = services::services();
	    N("Services: %d activated for %d registered", int(@$activated), int(@$l));
	},
	clicked => sub { 
	    require services;
	    $o->{services} = services::ask($o) and services::doit($o, $o->{services});
	},
    };

    my $check_complete = sub {
	require install::pkgs;
	my $p = install::pkgs::packageByName($o->{packages}, 'task-x11');
	$o->{raw_X} || !$::testing && $p && !$p->flag_installed ||
	$o->ask_yesorno('', N("You have not configured X. Are you sure you really want this?"));
    };

    $o->summary_prompt(\@l, $check_complete);

    install::steps::configureTimezone($o) if !$timezone_manually_set;  #- do not forget it.
}

#------------------------------------------------------------------------------
sub setRootPassword {
    my ($o, $clicked) = @_;
    my $sup = $o->{superuser} ||= {};
    $sup->{password2} ||= $sup->{password} ||= "";

    if ($o->{security} >= 1 || $clicked) {
	require authentication;
	authentication::ask_root_password_and_authentication($o, $o->{net}, $sup, $o->{authentication} ||= {}, $o->{meta_class}, $o->{security});
    }
    install::steps::setRootPassword($o);
}

#------------------------------------------------------------------------------
#-addUser
#------------------------------------------------------------------------------
sub addUser {
    my ($o, $clicked) = @_;
    $o->{users} ||= [];

    if ($o->{security} < 1) {
	push @{$o->{users}}, { password => 'mandrake', realname => 'default', icon => 'automagic' } 
	  if !member('mandrake', map { $_->{name} } @{$o->{users}});
    }
    if ($o->{security} >= 1 || $clicked) {
	my @suggested_names = @{$o->{users}} ? () : grep { !/^\./ && $_ ne 'lost+found' && -d "$::prefix/home/$_" } all("$::prefix/home");
	any::ask_users($o, $o->{users}, $o->{security}, \@suggested_names);
    }
    add2hash($o, any::get_autologin());
    any::autologin($o, $o);
    any::set_autologin($o->do_pkgs, $o->{autologin}, $o->{desktop}) if $::globetrotter;

    install::steps::addUser($o);
}

#------------------------------------------------------------------------------
sub setupBootloaderBefore {
    my ($o) = @_;
    my $_w = $o->wait_message('', N("Preparing bootloader..."));
    $o->SUPER::setupBootloaderBefore;
}

#------------------------------------------------------------------------------
sub setupBootloader {
    my ($o) = @_;
    if (arch() =~ /ppc/) {
	if (detect_devices::get_mac_generation() !~ /NewWorld/ && 
	    detect_devices::get_mac_model() !~ /IBM/) {
	    $o->ask_warn('', N("You appear to have an OldWorld or Unknown machine, the yaboot bootloader will not work for you. The install will continue, but you'll need to use BootX or some other means to boot your machine. The kernel argument for the root fs is: root=%s", '/dev/' . fs::get::root_($o->{fstab})->{device}));
	    log::l("OldWorld or Unknown Machine - no yaboot setup");
	    return;
	}
    }
    if (arch() =~ /^alpha/) {
	$o->ask_yesorno('', N("Do you want to use aboot?"), 1) or return;
	catch_cdie { $o->SUPER::setupBootloader } sub {
	    $o->ask_yesorno('', 
N("Error installing aboot, 
try to force installation even if that destroys the first partition?"));
	};
    } else {
	any::setupBootloader_simple($o, $o->{bootloader}, $o->{all_hds}, $o->{fstab}, $o->{security}) or return;
	any::installBootloader($o, $o->{bootloader}, $o->{all_hds}) or die "already displayed";
    }
}

sub miscellaneous {
    my ($o, $_clicked) = @_;

    if ($o->{meta_class} ne 'desktop' && $o->{meta_class} ne 'firewall' && !$o->{isUpgrade}) {
	require security::level;
	security::level::level_choose($o, \$o->{security}, \$o->{libsafe}, \$o->{security_user});

	if ($o->{security} > 2 && find { $_->{fs_type} eq 'vfat' } @{$o->{fstab}}) {
	    $o->ask_okcancel('', N("In this security level, access to the files in the Windows partition is restricted to the administrator."))
	      or goto &miscellaneous;
	}
    }

    install::steps::miscellaneous($o);
}

#------------------------------------------------------------------------------
sub configureX {
    my ($o, $expert) = @_;

    install::steps::configureXBefore($o);
    symlink "$::prefix/etc/gtk", "/etc/gtk";

    require Xconfig::main;
    my ($raw_X) = Xconfig::main::configure_everything_or_configure_chooser($o, install::any::X_options_from_o($o), !$expert, $o->{keyboard}, $o->{mouse});
    if ($raw_X) {
	$o->{raw_X} = $raw_X;
	install::steps::configureXAfter($o);
    }
}

#------------------------------------------------------------------------------
sub generateAutoInstFloppy {
    my ($o, $replay) = @_;
    my $img = install::any::getAndSaveAutoInstallFloppies($o, $replay) or return;

    my $floppy = detect_devices::floppy();
    $o->ask_okcancel('', N("Insert a blank floppy in drive %s", $floppy), 1) or return;

	my $_w = $o->wait_message('', N("Creating auto install floppy..."));
	require install::commands;
	install::commands::dd("if=$img", 'of=' . devices::make($floppy));
	common::sync();
}

#------------------------------------------------------------------------------
sub exitInstall {
    my ($o, $alldone) = @_;

    return $o->{step} = '' if !$alldone && !$o->ask_yesorno(N("Warning"), 
N("Some steps are not completed.

Do you really want to quit now?"), 0);

    install::steps::exitInstall($o);

    $o->exit unless $alldone;

    $o->ask_from_no_check(
	{
	 title => N("Congratulations"),
	 icon => 'banner-exit',
	 messages => formatAlaTeX(messages::install_completed()),
	 interactive_help_id => 'exitInstall',
	 ok => $::local_install ? N("Quit") : N("Reboot"),
	},      
	[
	 if_(arch() !~ /^ppc/,
	     { val => \ (my $_t1 = N("Generate auto install floppy")), clicked => sub {
		   my $t = $o->ask_from_list_(N("Generate auto install floppy"), 
N("The auto install can be fully automated if wanted,
in that case it will take over the hard drive!!
(this is meant for installing on another box).

You may prefer to replay the installation.
"), [ N_("Replay"), N_("Automated") ]);
		   $t and $o->generateAutoInstFloppy($t eq 'Replay');
	       }, advanced => 1 }),
	 { val => \ (my $_t2 = N("Save packages selection")), clicked => sub { install::any::g_default_packages($o) }, advanced => 1 },
	]
	) if $alldone;
}


#-######################################################################################
#- Misc Steps Functions
#-######################################################################################

1;