#!/usr/bin/perl # $Id: control-center 269021 2010-05-19 16:37:57Z eugeni $ # Copyright (C) 1999-2008 Mandriva # Daouda Lo # Damien Krotkine # Thierry Vignaud # Yves Duret # Copyright (C) 2011-2016 Mageia # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # force gtk+3 to use the x11 backend, especially on wayland (mga#19498) # we rely on xwayland as It's also needed in order to have working GtkSocket/GtkPlug BEGIN { $ENV{GDK_BACKEND} = 'x11' if exists $ENV{WAYLAND_DISPLAY} } use strict; use diagnostics; use lib qw(/usr/lib/libDrakX); use c; use standalone; use common; use detect_devices; use lang; use feature 'state'; use POSIX qw(:signal_h :sys_utsname_h :math_h :sys_wait_h :unistd_h); use Glib; use Glib::Object::Introspection; # perl_checker#: require urpm::args POSIX::sigprocmask(SIG_BLOCK, POSIX::SigSet->new(SIGCHLD)); # i18n: IMPORTANT: to get correct namespace (drakconf instead of libDrakX) BEGIN { unshift @::textdomains, 'drakconf' } use mygtk3 qw(gtknew); use ugtk3 qw(:create :dialogs :helpers :wrappers); use Cairo; use Pango; use Gtk3::WebKit2; use MDV::Control_Center; my (%tool_pids, %tool_feedback, $gurpmi_pid); my ($conffile, $class_install) = ('/etc/mcc.conf', '/etc/sysconfig/system'); my ($rootwin_width, $rootwin_height) = mygtk3::root_window_size(); my $default_width = $rootwin_width <= 800 ? 720 : 800; my $default_height = $rootwin_height <= 480 ? 420 : $rootwin_height <= 600 ? 523 : 600; my $min_width = $rootwin_width == 640 ? 620 : 680; my $min_height = $rootwin_height == 480 ? 420 : 500; require_root_capability() if !$::testing; # just to get root capabilities #------------------------------------------------------------- # read configuration, set themes, ... my %h = getVarsFromSh($conffile); my %class = getVarsFromSh($class_install); $h{LOGS} ||= bool2text($class{CLASS} eq 'expert' ? 1 : 0); $h{EXPERT_WIZARD} ||= 0; $h{HEIGTH} ||= $default_height; $h{WIDTH} ||= $default_width; my %option_values; $option_values{show_log} = text2bool($h{LOGS}); my $program; my ($i, $geometry, $save_html); foreach (@ARGV) { $i++; $program = $1 if /--start-with=(.*)/; if (/^--geometry$/) { $geometry = splice @ARGV, $i, 1; last; } $save_html = 1 if /--save-html-pages/; } add_icon_path("$themes_dir/default"); mygtk3::import_style_ressources(); my $css = "$themes_dir/default/mcc.css"; my $pl = Gtk3::CssProvider->new; $pl->load_from_path($css); Gtk3::StyleContext::add_provider_for_screen(Gtk3::Gdk::Screen::get_default(), $pl, Gtk3::STYLE_PROVIDER_PRIORITY_APPLICATION); my $branding = N("Mageia"); my $product_id = common::parse_LDAP_namespace_structure(cat_('/etc/product.id')); # allow OEM branding: $branding = translate($product_id->{distribution}); #------------------------------------------------------------- # Splash window: please wait ... my $window_splash = Gtk3::Window->new('popup'); $window_splash->signal_connect(delete_event => \&quit_global); $window_splash->set_title(N("%s Control Center", $branding)); $window_splash->set_position('center_always'); $window_splash->add(gtknew('Fixed', widget_name => 'Steps', children => [ [ gtknew('Image', file => 'splash_screen'), 0, 0 ], [ gtknew('Label', text => N("%s Control Center", $branding)), 21, 60 ], [ gtknew('Label', text => N("Loading... Please wait")), 21, 85 ], ]), ); $window_splash->show_all; gtkflush(); #------------------------------------------------------------- # Data structures my $more_wizard_dir = "/etc/wizard.d/"; my $isWiz = -e "/usr/sbin/drakwizard"; my $isRpmDrake = -e "/usr/bin/rpmdrake"; my $isParkRpmDrake = -e "/usr/sbin/park-rpmdrake"; my $isWebAdmin = -e "/usr/bin/mdkwebadmin"; my $isRfbDrake = -e "/usr/bin/rfbdrake"; my $isDrakStats = -e "/usr/sbin/drakstats"; my $application_driven_menu; sub is_wizard_installed { -f top(glob("/usr/share/perl5/vendor_perl/MDK/Wizard/$_[0]")) } my $is_auth_wizard_installed = is_wizard_installed('Nisautofs.pm'); sub wizard_format { map { my ($id, $wizard, $icon, $description, $long_description, $file) = @$_; $programs{$id} = { binary => "drakwizard $wizard", embedded => 1, description => $description, long_description => $long_description, icon => $icon, }; if_(is_wizard_installed($file), $id); } @_; } # [ [ class_label, class icon name, [ program_label, ... ] ] my @tree = ( if_($isRpmDrake || $isParkRpmDrake, [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Software Management"), 'software', 'software-management', [ if_($isRpmDrake, { title => N("Software Management"), list => [ "Install Software", "Mageia Update", if_(0, "Mageia Online"), "Updates Configuration", "Software Media Manager", ] }, ), { title => N("Others"), list => [ if_($isParkRpmDrake, "Manage park"), if_($isDrakStats, "Package stats"), ] }, ] ] ), if_(0, [ N("Server wizards"), 'wizard-mdk' ]), [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Sharing"), 'file-sharing-mdk', 'wiz-client', [ { title => N("Sharing"), list => [ (wizard_format( # [ id, wizard file name, icon, description ] [ "FTP wizard", "proftpd", 'ftp-mdk', N("Configure FTP"), N("Set up an FTP server"), 'Proftpd.pm', ], [ "Samba wizard", "samba", 'samba_server-mdk', N("Configure Samba"), N("Set up a file and print server for workstations running Linux and non-Linux systems"), 'Samba.pm', ], [ "Manage Samba share", "sambashare", 'wizard-mdk', N("Manage Samba shares"), N("Manage, create special share, create public/user shares"), 'Sambashare.pm', 1, ], [ "Web wizard", "apache2", 'web_server-mdk', N("Configure web server"), N("Set up a web server"), 'Apache.pm', ], [ "Installation server wizard", "installsrv", 'wizard-mdk', N("Configure installation server"), N("Set up server for network installations of %s", $branding), 'Installsrv.pm', 1, ], ), ), ] }, ] ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Network Services"), 'network-services-mdk', 'mcc-network', [ { title => N("Network Services"), list => [ (wizard_format( # [ id, wizard file name, icon, description ] [ "DHCP wizard", "dhcp", 'dhcp_server-mdk', N("Configure DHCP"), N("Set up a DHCP server"), 'Dhcp.pm', ], [ "DNS wizard", "bind", 'dns_server-mdk', N("Configure DNS"), N("Set up a DNS server (network name resolution)"), 'Bind.pm', ], [ "Squid wizard", "squid", 'drakproxy-mdk', N("Configure proxy"), N("Configure a web caching proxy server"), 'Squid.pm', ], [ "Time wizard", "ntp", 'ntp_server-mdk', N("Configure time"), N("Set the time of the server to be synchronized with an external time server"), 'Ntp.pm', ], [ "SSHD wizard", "sshd", 'wizard-sshd', N("OpenSSH daemon configuration"), N("OpenSSH daemon configuration"), 'Sshd.pm', ], ), ), ] }, ] ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Authentication"), 'drakauth-mdk', '', [ { title => N("Authentication"), list => [ if_($is_auth_wizard_installed, "Authentication"), (wizard_format( # [ id, wizard file name, icon, description ] [ 'Nis+autofs wizard', "nisautofs", 'nisautofs', N("Configure NIS and Autofs"), N("Configure the NIS and Autofs services"), 'Nisautofs.pm', ], [ "LDAP wizard", "ldap", "ldap-mdk", N("Configure LDAP"), N("Configure the LDAP directory services"), 'Ldap.pm', ], ), ), ] }, ] ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Groupware"), 'groupware-mdk', '', [ { title => N("Groupware"), list => [ (wizard_format( # [ id, wizard file name, icon, description ] if_(0, [ "News wizard", "inn", 'news-mdk', N("Configure news"), N("Configure a newsgroup server"), 'Inn.pm', ], [ "Kolab wizard", "kolab", "kolab-mdk", N("Configure groupware"), N("Configure a groupware server"), 'Kolab.pm', ]), [ "Postfix wizard", "postfix", 'postfix-mdk', N("Configure mail"), N("Configure the Internet mail services"), 'Postfix.pm' ], ), ), ] }, ] ], if_($isWebAdmin || $isRfbDrake, [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Online Administration"), 'online-administration-mdk', '', [ { title => N("Online Administration"), list => [ if_($isWebAdmin, (map { my ($id, $icon, $op, $description, $long_description) = @$_; $programs{$id} = { binary => "mdkwebadmin.pl $op", embedded => -1, # too big description => $description, long_description => $long_description, icon => $icon, }; $id; } ( # [ id, wizard file name, icon, description ] [ "Local Admin", 'local-administration-mdk', '--direct', N("Local administration"), (-e "/usr/bin/webmin" ? N("Configure the local machine via web interface") : N("You don't seem to have webmin installed. Local config is disabled")) ], [ "Remote Admin", 'remote-administration-mdk', '--link', N("Remote administration"), N("Click here if you want to configure a remote box via Web interface"), ]) ) ), if_($isRfbDrake, "Remote Control", ) ] }, ] ]), [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Hardware"), 'drakhard-mdk', 'mcc-hardware', [ { title => N("Manage your hardware"), list => [ "Hardware List", "Sound", ] }, { title => N("Configure graphics"), list => [ "3D", "Graphical server configuration", ] }, { title => N("Configure mouse and keyboard"), list => [ "Keyboard", "Mouse", ] }, { title => N("Configure printing and scanning"), list => [ "Printer", "Scanner", if_(-x real_bin_path("drakfax"), "Fax"), ] }, { title => N("Others"), list => [ "UPS", ] }, ] ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Network & Internet"), 'net-mdk', 'mcc-network', [ { title => N("Manage your network devices"), list => [ "Network Center", "Add Connection", if_(!-x real_bin_path("draknetcenter"), "Configure Internet", "Manage Connection", "Monitor Connection", ), "Remove Interface", ] }, { title => N("Personalize and Secure your network"), list => [ "Proxy Configuration", "Connection Sharing", "Network Profiles", "VPN", ] }, { title => N("Others"), list => [ "Hosts", ] }, ], ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("System"), 'system-mdk', 'mcc-system', [ { title => N("Manage system services"), list => [ if_(!$is_auth_wizard_installed, "Authentication"), "Services", "Fonts", ] }, { title => N("Localization"), list => [ "Date & Time", "Localization", ] }, { title => N("Administration tools"), list => [ "Logs", if_($ENV{LANGUAGE} !~ /^zh/, "Console"), "Users", "Migration", if_(-x real_bin_path("drakcronat"), "Programs scheduling"), 'Snapshots', "Writing ISO", "Virtualization", ] }, ] ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Network Sharing"), 'network-sharing-mdk', 'mcc-networksharing', [ { title => N("Configure Windows(R) shares"), list => [ "Access Windows shares", "Samba configuration", ] }, { title => N("Configure NFS shares"), list => [ "NFS mount points", "NFS exports", ] }, { title => N("Configure WebDAV shares"), list => [ "WebDAV mount points", ] }, ], ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Local disks"), 'partition-mdk', 'mcc-localdisks', [ { title => N("Local disks"), list => [ "Hard Drives", (map { my ($type, $scan, $text_orig, $long_text) = @$_; map_index { my $text = sprintf(translate($text_orig), $_->{info}); my $full_name = $text . ($::i ? $::i + 1 : ''); $programs{$full_name} = { binary => "diskdrake --removable=$_->{device}", embedded => 1, description => $text, long_description => sprintf(translate($long_text), $_->{info}), icon => "diskdrake_$type", }; $full_name; } $scan->(); } do { my %cdroms_by_type; foreach (detect_devices::cdroms()) { my $type = detect_devices::isBurner($_) ? 'burner' : detect_devices::isDvdDrive($_) ? 'DVD' : 'cdrom'; push @{$cdroms_by_type{$type}}, $_; } ([ 'cdrom', sub { @{$cdroms_by_type{cdrom} || []} }, N_("CD-ROM (%s)",), N_("Set where your \"%s\" CD-ROM drive is mounted"), ], [ 'dvd', sub { @{$cdroms_by_type{DVD} || []} }, N_("DVD-ROM (%s)"), N_("Set where your \"%s\" DVD-ROM drive is mounted"), ], [ 'cdwriter', sub { @{$cdroms_by_type{burner} || []} }, N_("CD/DVD burner (%s)"), N_("Set where your \"%s\" CD/DVD burner is mounted"), ], [ 'zip', \&detect_devices::zips, N_("ZIP drive"), N_("Set where your ZIP drive is mounted"), ], ); }), "Partition Sharing", ] }, ] ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Security"), 'security-mdk', 'mcc-security', [ { title => N("Security"), list => [ "Security Level", "Firewall", "Firewall6", "Mageia Tools Authentication", "Invictus Firewall", "Parental Controls", ] }, ] ], [ #-PO: please keep the following message very short: it must fit in the left list of MCC!!! N("Boot"), 'drakboot-mdk', 'mcc-boot', [ { title => N("Configure boot steps"), list => [ #if_(detect_devices::floppies, "Boot Disk"), # kernel is too big "Auto login Config", "Boot Config", "Display Manager chooser", ] }, { title => N("Others"), list => [ if_(0, "Auto Install"), ] }, ] ], ); if (scalar glob_("$more_wizard_dir/*.conf")) { my @leaf = ( N("Additional wizards"), 'wizard-mdk', [] ); foreach my $file (glob_("$more_wizard_dir/*.conf")) { next unless -f $file; my %tmp = getVarsFromSh($file); $programs{$tmp{NAME}} = { binary => "drakwizard " . lc($file), embedded => 1, description => $tmp{DESCRIPTION}, icon => $tmp{ICON} || 'wizard-mdk', long_description => $tmp{LONG_DESCRIPTION}, }; push(@{$leaf[2]}, $tmp{NAME}); } push(@tree, \@leaf); } #------------------------------------------------------------- # let build the GUI my $align = mygtk3::text_direction_rtl() ? "right" : "left"; my $align2 = mygtk3::text_direction_rtl() ? "right" : "left"; # main window : my ($timeout, %check_boxes, $emb_socket); # set default size: my $window_global = gtkset_size_request(Gtk3::Window->new('toplevel'), $min_width, $min_height); mygtk3::register_main_window($window_global); if ($geometry) { @h{qw(HEIGTH WIDTH)} = $geometry =~ /(\d+)x(\d+)/; my ($x, $y) = $geometry =~ /([+-]\d+)([+-]\d+)/; $window_global->move($x, $y) if $x || $y; } else { } $window_global->resize(max($default_width, $h{WIDTH}, $min_width), max($h{HEIGTH}, $default_height, $min_height)); $window_global->set_icon(gtkcreate_pixbuf("/usr/share/icons/hicolor/128x128/apps/drakconf.png")); my $pending_app = 0; my $help_on_context = 'drakconf-intro'; my $mga_rel = common::mageia_release(); my ($steps, $view); my $release = mageia_release_info(); sub run_help() { run_program::raw({ detach => 1, as_user => 1 }, 'drakhelp', '--id', $help_on_context); } sub run_browser { my ($url) = @_; $url = "http://wiki.mageia.org/en/$release->{distribution}_$release->{version}_$url" if $url !~ m!^http://!; run_program::raw({ detach => 1, as_user => 1 }, '/usr/bin/www-browser', $url); } my $ui = gtknew('UIManager', actions => [ # [name, stock_id, value, label, accelerator, tooltip, callback] [ 'FileMenu', undef, N("_File") ], [ 'Quit', undef, N("_Quit"), #-PO: "" must _NOT_ be translated. This is a keyboard shortcut for "Quit". #-PO: you just have to select the proper letter for your language (eg: english: "Quit" => "Q") N("Q"), undef, \&quit_global ], [ 'OptionsMenu', undef, N("_Options") ], [ 'HelpMenu', undef, N("_Help") ], [ 'Help', undef, N("_Help"), N("H"), undef, \&run_help ], [ 'Release_notes', undef, N("_Release notes"), undef, undef, sub { run_browser('Release_Notes') } ], [ 'What s New', undef, N("What's _New?"), undef, undef, sub { run_browser("What's New?") } ], [ 'Errata', undef, N("_Errata"), undef, undef, sub { run_browser('Errata') } ], [ 'Report Bug', undef, N("_Report Bug"), undef, undef, sub { run_program::raw({ detach => 1, as_user => 1 }, 'drakbug', '--report', (split(/\s/, $pending_app))[0] || 'drakconf'); } ], [ 'About', undef, N("_About..."), '', undef, \&about_mga_cc ], ], toggle_actions => [ [ 'show_log', undef, N("Display _Logs"), undef, undef, sub { $option_values{show_log} = $check_boxes{show_log}->get_active; if ($option_values{show_log}) { start_logdrake(); } else { kill_logdrake(); } } ], if_(0 && $isWiz, [ 'Expert_Wizard', undef, N("Expert mode in _wizards"), undef, undef, sub { $option_values{expert_wizard} = $check_boxes{wiz_expert}->get_active }, ], ), ], string => qq( )); my $menu = $ui->get_widget('/MenuBar'); %check_boxes = map { $_ => $ui->get_widget('/MenuBar/OptionsMenu/' . $_); } ('show_log', if_(0 && $isWiz, "wiz_expert")); my @buttons; my $offset = 15; $view = gtknew('WebKit2_WebView', no_popup_menu => 1); # disable plugins and the like: my $settings = $view->get_settings; $settings->set_enable_plugins(0); $settings->set_enable_java(0); $settings->set_enable_write_console_messages_to_stdout(1) if $::testing; $settings->set_allow_universal_access_from_file_urls(1); # start a program if given --start-with= option: $view->signal_connect('load-changed' => sub { my (undef, $msg) = @_; state $done; return if $done; return if $msg ne 'finished'; $done = 1; load_program() if $program; }); # so that it exists when building steps: my $banner_notebook = Gtk3::Notebook->new; build_list(); gtkadd($window_global, gtkpack_(Gtk3::VBox->new(0, 0), 0, $menu, #0, $banner_notebook, 0, Gtk3::HSeparator->new, # 0, gtkset_size_request(Gtk3::VBox->new(10, 10), -1, 2), 1, gtkpack_(Gtk3::HBox->new(0, 0), 1, $steps = gtknew('MDV_Notebook', parent_window => $window_global, children => [ #Layout Fixed # 145 is the vertical offset in order to be below the actual logo: [ gtknew('VBox', spacing => 0, width => (192 - $offset), children_tight => [ map { gtknew('HBox', spacing => 0, children => [ 1, $_, 0, gtknew('Alignment', width => 12), ]); } @buttons ]), 0, 100 ], ], right_child => gtknew('ScrolledWindow', child => gtkset_border_width($view, 5), no_shadow => 1, h_policy => 'never')), 1, gtkpack(my $emb_box = Gtk3::VBox->new(0, 0), my $wait_darea = gtkset_size_request( gtkpack_(gtknew('VBox'), 1, gtknew('VBox'), 0, my $wait_img = Gtk3::Image->new, 0, Gtk3::Label->new(N("Loading... Please wait")), 1, gtknew('VBox') ), -1, -1), ), ), 0, Gtk3::HSeparator->new, 0, my $buttons = gtkadd(gtkset_layout(Gtk3::HButtonBox->new, 'start'), map { gtkset_border_width($_, 3) } gtksignal_connect(my $cancel = Gtk3::Button->new(N("Cancel")), clicked => sub { stop_wait_area(); kill_children(); child_just_exited(); }), ), ) ); $view->set_size_request(-1, -1); $window_global->signal_connect(delete_event => \&quit_global); $window_global->add_accel_group($ui->get_accel_group); my $accel = Gtk3::AccelGroup->new; $accel->connect(Gtk3::Gdk::keyval_from_name('F1'), [], ['visible'], \&run_help); $window_global->add_accel_group($accel); my (undef, $nodename) = POSIX::uname(); $window_global->set_title(N("%s Control Center %s [on %s]", $branding, $version, $nodename)); $window_global->set_position('center'); foreach my $notebook ($banner_notebook) { $notebook->set_property('show-border', 0); $notebook->set_property('show-tabs', 0); } # banner : #add2notebook($banner_notebook, "", Gtk3::Banner->new("/usr/share/icons/hicolor/48x48/apps/drakconf.png", # N("Welcome to the %s Control Center", $branding))); my ($_hand_cursor, $wait_cursor) = map { Gtk3::Gdk::Cursor->new($_) } qw(hand2 watch); my $left_locked = 0; #my $spacing = 25; my %tool_callbacks; my ($page_count); my $conf_file = '/etc/sysconfig/mcc.conf'; foreach (cat_($conf_file)) { #s/^ENABLE_//; #my ($key, $val) = /^(.*)=(.*)/; if (my ($key, $val) = /^ENABLE_(.*)=(.*)/) { $key =~ s/_/ /g; #warn "--> ($key, $val)\n"; $programs{$key}{disabled} = 1 if $val ne 'yes'; warn ">> disabling $key\n" if $val ne 'yes'; } else { warn "bogus line in $conf_file: $_\n"; next; } } sub load_packages2install() { my $progs_conf_file = '/usr/share/mcc/progs.conf'; foreach (cat_($progs_conf_file)) { #if (my ($key, $val) = /^USE_WRAPPER_FOR_(.*)=(.*)/) { if (my ($key, $val) = /^INSTALL_FOR_(.*)=(.*)/) { $key =~ s/_/ /g; # we'll use gurpmi in order to install missing packages if needed; $programs{$key}{packages2install} = $val if $val; } else { warn "bogus line in $conf_file: $_\n"; next; } } } sub clean_list { my ($subtree) = @_; grep { my $stuff = $_; my $exec = real_bin_path($programs{$stuff}{binary}); my $is_present = -x $exec; if (!$is_present && $programs{$stuff}{packages2install}) { $is_present = 1; # override missing icon: $programs{$stuff}{real_icon} ||= $programs{$stuff}{icon}; $programs{$stuff}{icon} = 'installremoverpm'; } elsif ($programs{$stuff}{real_icon}) { $programs{$stuff}{icon} = $programs{$stuff}{real_icon}; } # do not complain about missing entries in move: warn qq("cannot run $exec" since it is not installed [$stuff]) if $mga_rel !~ /Move/ && !$is_present && !$programs{$stuff}{disabled}; !$programs{$stuff}{hidden} && $is_present && !$programs{$stuff}{disabled}; } @$subtree; } sub build_widget_element { my ($label) = @_; my $icon = $programs{$label}{icon}; die "$label 's icon is missing" if !$programs{$label} && $::testing; $tool_callbacks{$label} = sub { run_tool($label, undef, #$event_box, $icon, $programs{$label}{description}, $programs{$label}); }; # FIX ME: DO THIS AGAIN: $tool_feedback{$label} = sub {}; #sub { $event_box->get_window && $event_box->window->set_cursor($hand_cursor) }; my $real_icon = $icon ? '' : ''; my @widgets = ( qq($real_icon), qq(
) . escape_text_for_TextView_markup_format($programs{$label}{description}) . qq(
) ); qq( @widgets
); } my (@strings, $current_string_idx); sub load_view() { $view->load_html($strings[$current_string_idx], 'file:///'); } sub build_list() { my $index = 0; load_packages2install(); my $i; foreach (@tree) { my ($text, $_icon, $help, $subtrees) = @$_; my @subtrees = grep { $_->{list} = [ clean_list($_->{list}) ]; !is_empty_array_ref($_->{list}) } @$subtrees; # Skip empty classes: next if !@subtrees; $i++; my $my_index = $index++; my $square_icon_uri = mygtk3::_find_imgfile('cadre-ic'); my $back_img = mygtk3::_find_imgfile('right-white-background_right_part_768.png'); my $string = join("\n", qq( )); # Create right notebook pages : my $section; foreach my $subtree (@subtrees) { $string .= $section++ ? "
\n" : '
'; my $title = mygtk3::asteriskize($subtree->{title}); $string .= qq(
$title
); if (@{$subtree->{list}} % 2) { my @widgets = (pop @{$subtree->{list}}, ""); @widgets = reverse @widgets if mygtk3::text_direction_rtl(); push @{$subtree->{list}}, @widgets; } $string .= join("\n", map { "" . join("\n", grep { defined $_ } @$_) . "" } # "" . join("\n\n", grep { defined $_ } @$_) . "" } group_by(2, map { $_ ? build_widget_element($_) : '' } @{$subtree->{list}}) ); $string .= "
\n"; } # b/c we use Pango text markup language, we've to protect '&' caracter: $text =~ s/&/&/g; $page_count++; $string .= qq(); if ($save_html) { mkdir_p('/tmp/mcc'); MDK::Common::File::output_utf8("/tmp/mcc/$i.html", $string); } $strings[$my_index] = $string; push @buttons, gtknew('Button', relief => 'none', widget_name => 'StepsLabel', child => gtknew('Label_Right', text_markup => qq($text), ellipsize => 'end'), clicked => sub { $current_string_idx = $my_index; load_view(); $steps->move_selection($_[0]->get_child); $banner_notebook->set_current_page($my_index); $help_on_context = $help; }, ); } } # handle app clicks: $view->signal_connect('decide-policy' => sub { my (undef, $decision, $_type) = @_; # perl_checker: $decision = Gtk3::WebKit2::PolicyDecision2 my $res = 'true'; my $request = $decision->get_request; # get the actual program ID: my $url = eval { Glib::filename_from_uri($request->get_uri) }; $url =~ s!^/*!!; # not a path # workaround wekbit calling navigation-requested with "/": return if !$url; # url can be a translated UTF-8 string (eg: for CD/DVD entries): c::set_tagged_utf8($url); # do not crash on eg: "reload": if (!$tool_callbacks{$url}) { warn "Warning: invalid tool name: $url\n"; return $res; } # prevent WebKit2 to try to load 'app' page: $decision->ignore; $tool_callbacks{$url}->(); # FIXME: the following code is currently useless: # should we provide a way to kill buggy embedded programs ? return $res if $left_locked; warn_on_startup(); return $res; }); my %icons = ( ); # manage tools not present in MCC (localedrake, drakauth, ...): foreach my $label (difference2([ keys %programs ], [ keys %tool_callbacks ])) { my $text = $programs{$label}{description}; $tool_callbacks{$label} = sub { run_tool($label, undef, $icons{$label} || 'wizard-mdk', $text, $programs{$label}) }; $tool_feedback{$label} = sub {}; } foreach (keys %check_boxes) { my $widget = $check_boxes{$_}; $widget->set_active($option_values{$_}); } # "wait while launching a program" area : sub stop_wait_area() { $wait_darea->hide; if ($timeout) { Glib::Source->remove($timeout); undef $timeout; } } gtkflush(); # display first page (we use a timeout so that Y position of widgets is know): Glib::Timeout->add(100, sub { $buttons[0]->signal_emit('clicked'); 0 }); $window_global->show_all; hide_buttons(); $emb_box->hide; $wait_darea->realize; $SIG{USR1} = 'IGNORE'; $SIG{USR2} = 'IGNORE'; $SIG{TERM} = \&quit_global; $window_splash->destroy; undef $window_splash; Gtk3->main; sub load_program() { if (my $sub = $tool_callbacks{$program}) { Glib::Timeout->add(100, sub { $sub->(); 0 }); } else { err_dialog(N("Error"), N("Impossible to run unknown '%s' program", $program)); } } sub group_by { my $nb = shift @_; my @l; for (my $i = 0; $i < @_; $i += $nb) { push @l, [ map { $_[$_] } $i..$i+$nb-1 ]; # $_[$i], $_[$i+1], $_[$i+2] ]; } @l; } sub warn_on_startup() { if ($pending_app) { return if !splash_warning(N("The modifications done in the current module won't be saved."), 1); kill_children(); child_just_exited(); } } #------------------------------------------------------------- # socket/plug managment # called once embedded tool has exited sub child_just_exited() { $pending_app = 0; $left_locked = 0; if ($emb_socket) { $emb_socket->destroy; undef $emb_socket; } $emb_box->hide; hide_buttons(); $cancel->hide; gtkset_mousecursor_normal(); foreach my $w ($steps, $banner_notebook) { $w->show; } stop_wait_area(); } sub hide_socket_and_clean() { $emb_box->hide; $pending_app = 0; } sub create_hidden_socket { my ($icon, $label) = @_; my $banner; gtkpack_($emb_box, 0, $banner = gtkpack__(Gtk3::VBox->new(0, 0), Gtk3::Banner->new($icon, $label), Gtk3::HSeparator->new, ), 1, gtksignal_connect($emb_socket = Gtk3::Socket->new, 'plug-removed' => sub { $menu->show if $application_driven_menu; $banner->destroy; child_just_exited(); })); $banner->hide; # signal emitted when embedded apps begin to draw: $emb_socket->signal_connect('plug-added' => sub { $banner_notebook->hide; if ($application_driven_menu) { $menu->hide; } else { $banner->show; } stop_wait_area(); $left_locked = 0; hide_buttons(); $buttons->hide; select(undef, undef, undef, 0.002); # add delay to prevent black background with oxygen-gtk3 (mga#11969) return if !$emb_socket; $emb_socket->show; $emb_socket->set_can_focus(1); $emb_socket->grab_focus; #$emb_socket->get_window->XSetInputFocus; #only need by console (no more embedded until we've vte/zvt binding) }); $emb_box->set_focus_child($emb_socket); $emb_socket->hide; } #------------------------------------------------------------- # processes managment # embedded processes pid will be stocked there my @pid_launched; # logdrake pid are stocked here my $pid_exp; sub fork_ { my ($prog, $o_pid_table) = @_; $o_pid_table ||= \@pid_launched; my $pid = fork(); if (defined $pid) { !$pid and do { exec($prog) or POSIX::_exit(1) }; # immediate exit, else forked gtk+ object destructors will badly catch up parent mcc push @$o_pid_table, $pid; return $pid; } else { splash_warning(N("cannot fork: %s", "$!")); child_just_exited(); } } sub real_bin_path { my ($prog) = @_; $prog = first(split /\s+/, $prog); return $prog if $prog =~ m!/!; return "/usr/bin/$prog" if -x "/usr/bin/$prog"; return "/usr/sbin/$prog"; } sub run_tool { my ($label, $box, $icon, $text, $tool) = @_; my ($exec, $gtkplug) = @$tool{qw(binary embedded)}; $application_driven_menu = $tool->{application_driven_menu}; return if $tool_pids{$label}; state $done; if (!$done) { $done = 1; $SIG{CHLD} = \&sig_child; POSIX::sigprocmask(SIG_UNBLOCK, POSIX::SigSet->new(SIGCHLD)); } my $will_run_gurpmi; if (! -x real_bin_path($exec)) { if ($tool->{packages2install}) { # gurpmi doesn't support being embedded yet: $gtkplug = -1; $exec = join(' ', "/usr/bin/gurpmi", split(/\s/, $tool->{packages2install})); $will_run_gurpmi = 1; } else { splash_warning(N("cannot fork and exec \"%s\" since it is not executable", $exec)); return; } } $exec .= " --summary" if $option_values{expert_wizard} && $exec =~ /drakwizard/; my $embedded = $gtkplug != -1; # not "explicitely not embedded" if ($embedded) { $steps->hide; create_hidden_socket($icon, $text); $emb_box->show; $emb_socket->realize; $pending_app = $tool->{binary}; if ($gtkplug > 0) { $buttons->show; $exec .= " --embedded " . $emb_socket->get_id; $wait_darea->show; $cancel->show; my $run_pixbuf = eval { gtkcreate_pixbuf($icon . "_128") }; my $anim = Gtk3::Gdk::PixbufSimpleAnim->new(128,128, 1000/35); $anim->add_frame(mygtk3::_pixbuf_render_alpha($run_pixbuf, 255-$_*5)) foreach 0..31; # fade away $anim->add_frame(mygtk3::_pixbuf_render_alpha($run_pixbuf, 95+$_*5)) foreach 0..31; # fade back $anim->set_loop(1); $wait_img->set_from_animation($anim); $left_locked = 1; $buttons->show; $tool_pids{$label} = fork_($exec); } } else { # not embedded # fix #3415 when $gtkplug eq -1 local $option_values{embedded} = 0; $tool_pids{$label} = fork_($exec); $gurpmi_pid = $tool_pids{$label} if $will_run_gurpmi; } start_logdrake(); $box->get_window->set_cursor($wait_cursor) if $box; } sub start_logdrake() { # (re)start logdrake if needed if ($option_values{show_log} && !$pid_exp) { my $exec_log = "logdrake --explain=drakxtools"; $pid_exp = fork_($exec_log, []); } } sub kill_them_all { foreach my $pid (@_) { kill('TERM', $pid) if $pid; } } sub kill_children() { kill_them_all(@pid_launched); } sub kill_logdrake() { kill_them_all($pid_exp) if $pid_exp; } sub quit_global() { return 1 if mygtk3::quit_popup(); if (@pid_launched) { &kill_children(); return 1; # tell gtk+ not to quit } &kill_children(); &kill_logdrake(); my ($x, $y) = $window_global->get_size; setVarsInSh($conffile, { LOGS => bool2text($option_values{show_log}), EXPERT_WIZARD => bool2text($option_values{expert_wizard}), HEIGTH => $y, WIDTH => $x, }); gtkset_mousecursor_normal(); # Workaround a segfault on exit by manually destroying the window before exit() $window_global->destroy; standalone::exit(0); } #------------------------------------------------------------- # signals managment # got when child died and gone in zombie state sub sig_child { my ($_sig, $o_is_cleaner) = @_; my $child_pid; do { $child_pid = waitpid(-1, POSIX::WNOHANG); if (my $tool = find { $tool_pids{$_} eq $child_pid } keys %tool_pids) { $tool_feedback{$tool}->(); delete $tool_pids{$tool}; @pid_launched = grep { $_ ne $child_pid } @pid_launched; if ($child_pid == $gurpmi_pid) { undef $gurpmi_pid; my @services; if ($programs{$tool}{services_to_start_after_install}) { @services = @{$programs{$tool}{services_to_start_after_install}}; run_program::raw({ detach => 1 }, 'service', $_, 'restart') foreach @services; } # refresh the icon list if some package got installed (eg: system-config-printer) build_list(); load_view(); } } undef $pid_exp if $pid_exp eq $child_pid; } while $child_pid > 0; # child unexpectedly died (cleanup since child_just_exited won't be called by plug-removed since plug never was added) return if $o_is_cleaner || !$left_locked; child_just_exited(); splash_warning(N("This program has exited abnormally")); } #------------------------------------------------------------- # mcc dialog specific functions sub splash_warning { my ($label, $o_cancel_button) = @_; warn_dialog(N("Warning"), $label, { cancel => $o_cancel_button }); } sub new_dialog { my ($title, $o_no_button) = @_; my $dialog = gtkset_border_width(Gtk3::Dialog->new, 10); $dialog->set_transient_for($window_global); $dialog->set_position('center-on-parent'); $dialog->set_title($title); $dialog->get_action_area->pack_start(gtkadd(Gtk3::HButtonBox->new, gtksignal_connect(Gtk3::Button->new_with_label(N("Close")), clicked => sub { $dialog->destroy }) ), 0,0,0) unless $o_no_button; gtkset_modal($dialog, 1); } sub about_mga_cc() { my $window_about = new_dialog(N("About - %s Control Center", $branding)); my $tree_model = Gtk3::TreeStore->new("Glib::String", "Glib::String", "Glib::String"); my $credits = Gtk3::TextView->new; my $list = Gtk3::TreeView->new_with_model($tree_model); $list->set_can_focus(0); each_index { $list->append_column(Gtk3::TreeViewColumn->new_with_attributes("", Gtk3::CellRendererText->new, 'text' => $::i)) } 0..1; $list->set_headers_visible(0); foreach my $row ([ N("Authors: "), '' ], [ '', 'Daouda Lo' ], [ '', 'Thierry Vignaud' ], [ '', 'Yves Duret' ], ) { $tree_model->append_set(undef, [ map_index { $::i => $_ } @$row ]); } my ($previous_type, $not_first_title, $not_first_block); my $locale = lang::read; foreach my $line (grep { $_ ne "\n" && !/^#/ } cat_(top(glob("/usr/share/doc/mageia-release-*/CREDITS"), glob("/usr/share/doc/mageia-release-*/CREDITS.$locale->{lang}")))) { if (my ($type, $comment, $contributor) = split(/\|/, $line, 3)) { last if !$type; $comment =~ s/^ //; # fix initial space of first section (CREDITS format should be enhanced to specify lines that really are sections) chomp($contributor); if ($previous_type ne $type) { gtktext_append($credits, [ [ join('', if_($not_first_title, "\n"), translate(common::to_utf8($type)), "\n"), { 'weight' => 800, scale => '1.2' } ] ]); $previous_type = $type; $not_first_title = 1; } if ($contributor) { gtktext_append($credits, #-PO: this is used as "language: translator" in credits part of the about dialog: N("- %s: %s\n", translate($comment), translate($contributor)) ); } else { $comment = $comment . "\n" if !$not_first_block; # fix spacing before second title; gtktext_append($credits, join('', if_($not_first_block, "- "), translate($comment))); $not_first_block = 1; } } } # Give our translators the ability to show their family and # friends that thez participated ... #-PO Add your Name here to find it in the About section in your language. my $translator_name = N("_: NAME OF TRANSLATORS\nYour names"); #-PO Add your E-Mail address here if you want to show it in the about doialog. my $translator_email = N("_: EMAIL OF TRANSLATORS\nYour emails"); if ($translator_name ne "Your names" && 0) { $tree_model->append_set(undef, [ 0 => $_->[0], 1 => $_->[1] ]) foreach [ '', '' ], [ N("Translator: ") ]; $tree_model->append_set(undef, [ 0 => $_->[0], 1 => $_->[1] ]) foreach [ '', $translator_name, $translator_email ]; } $list->get_selection->set_mode('none'); gtkpack_($window_about->get_child, (0, Gtk3::Banner->new('/usr/share/icons/hicolor/48x48/apps/drakconf.png', #-PO: Here, first %s will be replaced by 'Mageia' #-PO: second %s will be replaced by the version (eg: "Mageia 1 (Free) Control Center") N("%s %s (%s) Control Center", $branding, $release->{version}, $release->{product}))), 0, Gtk3::Label->new(""), 0, Gtk3::Label->new( #-PO: here %s is eg: "1999-2008" N("Copyright (C) %s Mandriva SA", '1999-2008') . "\n" . N("Copyright (C) %s Mageia", '2011-2016') . "\n"), 0, Gtk3::HSeparator->new, 0, Gtk3::Label->new(""), 1, my $n = Gtk3::Notebook->new, ); add2notebook($n, N("Authors"), $list); add2notebook($n, N("Mageia Contributors"), gtkset_size_request(create_scrolled_window($credits), 650, 50)); $n->set_current_page(0); $window_about->show_all; } #------------------------------------------------------------- # mcc specific graphic functions: sub hide_buttons() { $buttons->hide; $cancel->hide; } #------------------------------------------------------------- # mcc specific graphic functions: sub render_shiner { my ($pixbuf, $shine_value) = @_; my $new_pixbuf = (mygtk3::_new_alpha_pixbuf($pixbuf))[2]; $pixbuf->saturate_and_pixelate($new_pixbuf, $shine_value, 0); $new_pixbuf; } sub scale { my ($pixbuf, $gain) = @_; my ($width, $height) = ($pixbuf->get_height, $pixbuf->get_width); $pixbuf->scale_simple($height+$gain, $width+$gain, 'hyper'); } 1;