#!/usr/bin/perl # $Id$ # Copyright (C) 1999-2004 MandrakeSoft # Daouda Lo # Damien Krotkine # Thierry Vignaud # Yves Duret # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. use strict; use diagnostics; use lib qw(/usr/lib/libDrakX); use standalone; use common; use detect_devices; use lang; use network::netconnect; # for profiles # i18n: IMPORTANT: to get correct namespace (drakconf instead of libDrakX) BEGIN { unshift @::textdomains, 'drakconf' } use ugtk2 qw(:create :dialogs :helpers :wrappers); #------------------------------------------------------------- # paths my ($bindir, $sbindir, $xbindir) = ("/usr/bin", "/usr/sbin", "/usr/X11R6/bin"); my $mcc_dir = "/usr/share/mcc"; my $themes_dir = "$mcc_dir/themes/"; my (%tool_pids, %tool_feedback); my ($version, $conffile, $class_install) = ("10", "/etc/mcc.conf", "/etc/sysconfig/system"); my ($default_heigth, $default_width) = (523, 720); require_root_capability(); # just to get root capabilities #------------------------------------------------------------- # read configuration, set themes, ... my %h = getVarsFromSh($conffile); my %class = getVarsFromSh($class_install); $h{THEME} ||= 'default'; $h{LOGS} ||= bool2text($class{CLASS} eq 'expert' ? 1 : 0); $h{EXPERT_WIZARD} ||= 0; $h{HEIGTH} ||= $default_heigth; $h{WIDTH} ||= $default_width; my %option_values; $option_values{show_log} = text2bool($h{LOGS}); my $theme = $h{THEME}; $theme = $1 if "@ARGV" =~ /--theme (\w+)/; my ($i, $geometry); foreach (@ARGV) { $i++; if (/^--geometry$/) { $geometry = splice @ARGV, $i, 1; last; } } -d "$themes_dir/$theme" or $theme = 'default'; add_icon_path("$themes_dir/$theme/"); add_icon_path("$themes_dir/default") if $theme ne 'default'; # fall back if theme miss some icons my $rc = find { -r $_ } ("$themes_dir/$theme/gtkrc", if_($theme ne 'default', "$themes_dir/default/gtkrc")); Gtk2::Rc->parse($rc) if -r $rc; #------------------------------------------------------------- # Splash window: please wait ... my $window_splash = Gtk2::Window->new('popup'); $window_splash->signal_connect(delete_event => \&quit_global); $window_splash->set_title(N("Mandrakelinux Control Center") . $version); $window_splash->set_position('center_always'); $window_splash->add(gtkadd(gtkset_shadow_type(Gtk2::Frame->new, 'etched_out'), gtkput(my $fixed = Gtk2::Fixed->new, gtkpack(Gtk2::VBox->new(0, 0), if_(-r "$themes_dir/$theme/splash_screen.png", gtkcreate_img("splash_screen")), gtkset_markup(Gtk2::Label->new, join('', qq(\n), N("Mandrakelinux Control Center"), ""), ), Gtk2::Label->new(N("Loading... Please wait") . "\n"), ), 0, 0), ) ); $fixed->set_has_window(1); $fixed->signal_connect(realize => sub { ugtk2::set_back_pixmap($fixed) }); $window_splash->show_all; gtkflush(); #------------------------------------------------------------- # Data structures my $isWiz = -e "/usr/sbin/drakwizard"; my $isRpmDrake = -e "/usr/sbin/rpmdrake"; my $isParkRpmDrake = -e "/usr/sbin/park-rpmdrake"; my $isWebAdmin = -e "/usr/bin/mdkwebadmin"; my $isRfbDrake = -e "/usr/X11R6/bin/rfbdrake"; # { key => [ log_exp, binary, gtkplug?, description ] } # { key => [ log_exp, [ binary, win_nb ], gtkplug?, description ] } # gtkplug meaning: -1 => not embedded, 0 => external x11 app, 1 => proper embedding my $exec_hash = { "Auto Install" => [ "drakautoinst", "$sbindir/drakautoinst", 1, N("Auto Install floppy") ], "Auto login Config" => [ "drakboot", "$sbindir/drakboot", 1, N("Autologin") ], "Backups" => [ "drakbackup", "$sbindir/drakbackup", 1, N("Backups") ], "Boot Config" => [ "drakboot", "$sbindir/drakboot --boot", 1, N("Boot loader") ], "Boot Theme" => [ "drakboot", "$sbindir/drakboot --splash", 1, N("Boot theme") ], "Boot Disk" => [ "drakfloppy", "$sbindir/drakfloppy", 1, N("Boot floppy") ], "Connection Sharing" => [ "drakgw", "$sbindir/drakgw", 1, N("Internet connection sharing") ], "Add Connection" => [ "drakconnect", "$sbindir/drakconnect --wizard", 1, N("New connection") ], "Manage Connection" => [ "drakconnect", "$sbindir/drakconnect --skip-wizard", 1, N("Manage connections") ], "Monitor Connection" => [ "net_monitor", "$sbindir/net_monitor", 1, N("Monitor connections") ], "Configure Internet" => [ "drakconnect", "$sbindir/drakconnect --internet", 1, N("Internet access") ], # little workaround to avoid drakconf freeze "Console" => [ "drakconsole", "$sbindir/drakconsole", 1, N("Console") ], #The Console will help you to solve issues "Date & Time" => [ "clock", "$sbindir/clock.pl", 1, N("Date and time") ], "Display Manager chooser" => [ "drakedm", "$sbindir/drakedm", 1, N("Display manager") ], "Fax" => [ "drakfax", "$sbindir/drakfax_server", 1, N("Fax") ], "Firewall" => [ "drakfirewall", "$sbindir/drakfirewall", 1, N("Firewall") ], "Fonts" => [ "drakfont", "$sbindir/drakfont", 1, N("Fonts") ], "Graphical server configuration" => [ "XFdrake", "$sbindir/XFdrake", 1, N("Graphical server") ], "Hard Drives" => [ "diskdrake", "$sbindir/diskdrake --hd", 1, N("Partitions") ], "Hardware List" => [ "harddrake", "$sbindir/harddrake2", 1, N("Hardware") ], "Install Software" => [ "rpmdrake", "$sbindir/rpmdrake", 1, N("Install") ], "Keyboard" => [ "keyboarddrake", "$sbindir/keyboarddrake", 1, N("Keyboard") ], "Kolab" => [ "kolab", "$sbindir/kolab", 1, N("Kolab") ], "Logs" => [ "logdrake", "$sbindir/logdrake", 1, N("Logs") ], "Manage park" => [ "rpmdrake", "$sbindir/park-rpmdrake", 1, N("Manage computer group") ], "Mandrake Update" => [ "rpmdrake", "$sbindir/MandrakeUpdate", 1, N("Updates") ], "Menus" => [ "menudrake", "$bindir/menudrake", -1, N("Menus"), "$bindir/menudrake" ], "Monitor" => [ "XFdrake", "$sbindir/XFdrake monitor", 1, N("Monitor") ], "Mouse" => [ "mousedrake", "$sbindir/mousedrake", 1, N("Mouse") ], "NFS mount points" => [ "diskdrake", "$sbindir/diskdrake --nfs", 1, N("NFS mount points") ], "Partition Sharing" => [ "diskdrake", "$sbindir/diskdrake --fileshare", 1, N("Local disk sharing") ], "Printer" => [ "printerdrake", "$sbindir/printerdrake", -1, N("Printers"), "$sbindir/printerdrake" ], "Programs scheduling" => [ "drakcronat", "/usr/X11R6/bin/drakcronat", 1, N("Scheduled tasks") ], #DrakCronAt enables to schedule Programs execution through crond and atd daemons "Proxy Configuration" => [ "drakproxy", "$sbindir/drakproxy", 1, N("Proxy") ], #for files and web browsing "Remote Control" => [ "rfbdrake", "$xbindir/rfbdrake", 1, N("Remote Control (Linux/Unix, Windows)") ], "Remove Interface" => [ "drakconnect", "$sbindir/drakconnect --del", 1, N("Remove a connection") ], "Remove Software" => [ "rpmdrake", "$sbindir/rpmdrake-remove", 1, N("Remove") ], "Resolution" => [ "XFdrake", "$sbindir/XFdrake resolution", 1, N("Screen resolution") ], "Samba mount points" => [ "diskdrake", "$sbindir/diskdrake --smb", 1, N("Samba mount points") ], "Scanner" => [ "scannerdrake", "$sbindir/scannerdrake", 1, N("Scanners") ], "Security Level" => [ "draksec", "$sbindir/draksec", 1, N("Level and checks") ], "Security Permissions" => [ "drakperm", "$sbindir/drakperm", 1, N("Permissions") ], "Services" => [ "drakxservices", "$sbindir/drakxservices", 1, N("Services") ], "Software Media Manager" => [ "rpmdrake", "$sbindir/edit-urpm-sources.pl", 1, N("Media Manager") ], "TV Cards" => [ "drakxtv", "$sbindir/drakxtv", 1, N("TV card") ], "UPS" => [ "drakups", "$sbindir/drakups", 1, N("UPS") ], "Users" => [ "userdrake", "$bindir/userdrake", -1, N("Users and groups") ], # too big "WebDAV mount points" => [ "diskdrake", "$sbindir/diskdrake --dav", 1, N("WebDAV mount points") ], }; # [ [ class_label, class icon name, [ [ program_label, program icon name ] ... ] ] ] my @tree = ([ N("Boot"), 'boot-mdk', [ if_(0 && detect_devices::floppies, [ "Boot Disk", 'drakfloppy-mdk', ]), [ "Auto login Config", 'autologin-mdk', ], [ "Boot Config", 'drakboot-mdk', ], [ "Boot Theme", 'boot-theme-mdk', ], if_(0, [ "Auto Install", 'drakautoinst-mdk', ]), ] ], [ N("Hardware"), 'drakhard-mdk', [ [ "Hardware List", 'harddrake-mdk', ], [ "Monitor", 'configure-monitor-mdk', ], [ "Resolution", 'resolution-mdk', ], [ "Graphical server configuration", 'XFdrake-mdk', ], if_(-x "$bindir/drakfax", [ "Fax", 'drakfax-mdk', ]), [ "TV Cards", 'tv-mdk', ], [ "Keyboard", 'keyboard-mdk', ], [ "Mouse", 'mousedrake-mdk', ], [ "Printer", 'printer-mcc-mdk', ], [ "Scanner", 'scanner-mdk', ], if_(0, [ "UPS", 'drakups-mdk', ]), ] ], [ N("Mount Points"), 'partition-mdk', [ [ "Hard Drives", 'diskdrake_hd', ], (map { my ($type, $name, $scan, $text) = @$_; map_index { my $full_name = $name . ($::i ? $::i + 1 : ''); $exec_hash->{$full_name} = [ "diskdrake", "$sbindir/diskdrake --removable=$_->{device}", 1, $text ]; [ $full_name, "diskdrake_$type" ]; } $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', N("CD-ROM"), sub { @{$cdroms_by_type{cdrom} || []} }, N("CD-ROM") ], [ 'dvd', N("DVD"), sub { @{$cdroms_by_type{DVD} || []} }, N("DVD-ROM") ], [ 'cdwriter', N("CD Burner"), sub { @{$cdroms_by_type{burner} || []} }, N("CD/DVD") ], [ 'floppy', N("Floppy"), \&detect_devices::floppies, N("Floppy drive") ], [ 'zip', N("Zip"), \&detect_devices::zips, N("ZIP drive") ], ), }), [ "NFS mount points", 'diskdrake_nfs', ], [ "Samba mount points", 'diskdrake_samba', ], [ "WebDAV mount points", 'webdav-mdk', ], [ "Partition Sharing", 'diskdrake_fileshare', ], ] ], [ N("Network & Internet"), 'net-mdk', [ [ "Add Connection", 'new-connection-mdk', ], [ "Configure Internet", 'internet-access-mdk', ], [ "Manage Connection", 'manage-connections-mdk', ], [ "Monitor Connection", 'net_monitor-mdk', ], [ "Remove Interface", 'remove-connection-mdk', ], [ "Proxy Configuration", 'drakproxy-mdk', ], [ "Connection Sharing", 'drakgw-mdk', ], ], ], [ N("Security"), 'security-mdk', [ [ "Security Level", 'draksec-mdk', ], [ "Security Permissions", 'drakperm-mdk', ], [ "Firewall", 'firewall-mdk', ], ] ], [ N("System"), 'system-mdk', [ [ "Menus" , 'menudrake-mdk', ], [ "Display Manager chooser", 'drakedm-mdk', ], [ "Services" , 'service-mdk', ], [ "Fonts", 'drakfont-mdk', ], [ "Date & Time" , 'time-mdk', ], [ "Logs", 'logdrake-mdk', ], if_($ENV{LANGUAGE} !~ /^zh/, [ "Console", 'console-mdk', ]), [ "Users", 'user-mdk', ], if_(-x "/usr/X11R6/bin/drakcronat", [ "Programs scheduling", 'drakcronat-mdk', ]), [ "Backups", 'backup-mdk', ], # [ "RFBDrake", 'unknown-mdk' ], ] ], if_($isRpmDrake || $isParkRpmDrake, [ N("Software Management"), 'software', [ if_($isRpmDrake, [ "Install Software", 'rpmdrake' ], [ "Remove Software", 'rpmdrake-remove' ], [ "Mandrake Update", 'MandrakeUpdate' ], [ "Software Media Manager", 'source-manager' ], ), if_($isParkRpmDrake, [ "Manage park", 'drakpark-mdk' ]), ] ]), if_($isWiz, [ N("Server wizards"), 'wizard-mdk', [ (map { my ($id, $wizard, $icon, $description) = @$_; $exec_hash->{$id} = [ "drakwizard", "$sbindir/drakwizard $wizard", 1, $description ]; [ $id, $icon ]; } (# [ id, wizard file name, icon, description ] [ "DHCP wizard", "dhcp", 'dhcp_server-mdk', N("Configure DHCP") ], [ "DNS wizard", "bind", 'dns_server-mdk', N("Configure DNS") ], [ "FTP wizard", "proftpd", 'ftp-mdk', N("Configure FTP") ], [ "News wizard", "inn", 'news-mdk', N("Configure news") ], [ "Kolab", "kolab", "kolab-mdk", N("Configure groupware") ], [ "Postfix wizard", "postfix", 'postfix-mdk', N("Configure mail") ], [ "Squid wizard", "squid", 'drakproxy-mdk', N("Configure proxy") ], [ "Samba wizard", "samba", 'samba_server-mdk', N("Configure Samba") ], [ "Time wizard", "ntp", 'ntp_server-mdk', N("Configure time") ], [ "Web wizard", "apache2", 'web_server-mdk', N("Configure web") ]), [ 'Nis+autofs', "nisautofs", 'nisautofs', N("Configure NIS and Autofs") ], [ "Install server", "installsrv", 'wizard-mdk', N("Configure installation server") ], [ "Pxe", "pxe", 'pxe-server', N("Configure PXE") ], ) ] ]), if_($isWebAdmin || $isRfbDrake, [ N("Online Administration"), 'online-administration-mdk', [ if_($isWebAdmin, (map { my ($id, $icon, $op, $description) = @$_; $exec_hash->{$id} = [ "mdkwebadmin", "$bindir/mdkwebadmin.pl $op", -1, $description ]; [ $id, $icon ]; } (# [ id, wizard file name, icon, description ] [ "Local Admin", 'local-administration-mdk', '--direct', N("Local administration") ], [ "Remote Admin", 'remote-administration-mdk', '--link', N("Remote administration") ]) ) ), if_($isRfbDrake, ["Remote Control", 'internet-access-mdk'] ) ] ]), ); #------------------------------------------------------------- # let build the GUI # main window : my ($timeout, %check_boxes, $emb_socket, $page_id); my $window_global = gtkset_size_request(Gtk2::Window->new('toplevel'), 620, 460); if ($geometry) { my ($w, $h) = $geometry =~ /(\d+)x(\d+)/; my ($x, $y) = $geometry =~ /([+-]\d+)([+-]\d+)/; $window_global->resize(max($w, $default_width), max($default_heigth, $h)); $window_global->set_uposition($x, $y) if $x || $y; } else { $window_global->resize(max($default_width, $h{WIDTH}), max($h{HEIGTH}, $default_heigth)); } $window_global->set_icon(gtkcreate_pixbuf("/usr/share/icons/drakconf.png")); my $pending_app = 0; my $help_on_context = 'drakconf-intro'; #Please replace with correct contextual help page my @ctx = qw(drakconf-intro mcc-boot mcc-hardware mcc-mountpoints mcc-network mcc-security mcc-system software-management wiz-client); my @themes = grep { -d "$themes_dir/$_" } all($themes_dir); #-PO Translators, please keep all "/" charaters !!! my %options = ( 'show_log' => [ N("/_Options"), N("/Display _Logs") ], 'embedded_mode' => [ N("/_Options"), N("/_Embedded Mode") ], 'wiz_expert' => [ N("/_Options"), N("/Expert mode in _wizards") ], ); my %shared_translations = ( "profiles" => N("/_Profiles"), "delete" => N("/_Delete"), "new" => N("/_New"), ); # for profiles: my ($netcnx, $netc, $intf) = ({}, {}, {}); my @profiles; my $mdk_rel = common::mandrake_release(); my @menu_items = ( [ N("/_File"), undef, undef, undef, '' ], [ N("/_File") . N("/_Quit"), N("Q"), \&quit_global, undef, '', N("Quit") ], [ N("/_Options"), undef, undef, undef, '' ], [ join('', @{$options{show_log}}), undef, sub { $option_values{show_log} = $check_boxes{show_log}->get_active; start_logdrake(); }, undef, '' ], if_(0 && $isWiz, [ join('', @{$options{wiz_expert}}), undef, sub { $option_values{expert_wizard} = $check_boxes{wiz_expert}->get_active }, undef, '', ], ), if_(@themes > 1, [ N("/_Themes"), undef, undef, undef, '' ], (map { my $name = $_; [ N("/_Themes") . "/" . ($name eq $theme ? " O " : " ") . "_$_", undef, sub { return if $theme eq $name; !$pending_app || splash_warning(N("This action will restart the control center.\nAny change not applied will be lost."), 1) and do { # embedded app must be killed kill_children(); kill_logdrake(); child_just_exited(); exec "$0 --theme $name"; }; }, undef, '' ] } @themes), [ N("/_Themes").N("/_More themes"), undef, \&more_themes, undef, '' ] ), [ $shared_translations{profiles}, undef, undef, undef, '' ], [ $shared_translations{profiles} . $shared_translations{new}, undef, sub { my $dialog = _create_dialog(N("New profile..."), { small => 1, transient => $window_global }); my $entry_dialog = Gtk2::Entry->new; gtkpack($dialog->vbox, Gtk2::WrappedLabel->new(N("Name of the profile to create (the new profile is created as a copy of the current one):")), $entry_dialog, ); gtkpack($dialog->action_area, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { $dialog->destroy }), gtksignal_connect(my $bok = Gtk2::Button->new(N("Ok")), clicked => sub { my $prof = $entry_dialog->get_text; # netprofile does not like spaces in profile names... $prof =~ s/ /_/g; # warn if already existing: if (member($prof, @profiles)) { err_dialog(N("Error"), N("The \"%s\" profile already exists!", $prof), { transient => $dialog }); return 1; } network::netconnect::add_profile($netcnx, $prof); update_profiles(); $dialog->destroy; }), ); $bok->can_default(1); $bok->grab_default; $dialog->show_all; $dialog->run; update_profiles(); }, undef, '' ], [ $shared_translations{profiles} . $shared_translations{delete}, undef, sub { return if !$window_global->realized && $netcnx->{PROFILE} ne "default"; my $dialog = _create_dialog(N("Delete profile"), { stock => 'gtk-dialog-warning' }); gtkpack($dialog->vbox, Gtk2::Label->new(N("Profile to delete:")), my $combo_dialog = Gtk2::OptionMenu->new, ); $combo_dialog->set_popdown_strings(grep { ! /default/ } network::netconnect::get_profiles()); gtkpack($dialog->action_area, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { $dialog->destroy }), gtksignal_connect(Gtk2::Button->new(N("Ok")), clicked => sub { my $profile2delete = $combo_dialog->entry->get_text; if ($profile2delete eq $netcnx->{PROFILE}) { err_dialog(N("Warning"), N("You can not delete the current profile")); return 1; } $dialog->destroy; Gtk2->main_quit; network::netconnect::del_profile($profile2delete); update_profiles(1); }), ); $dialog->show_all; $dialog->run; return; }, undef, '' ], [ join('/', $shared_translations{profiles}, ""), undef, undef, undef, '' ], [ N("/_Help"), undef, undef, undef, '' ], [ N("/_Help").N("/_Help"), undef, sub { fork_("drakhelp --id $help_on_context") }, undef, '', N("Help") ], [ N("/_Help").N("/_Report Bug"), undef, sub { fork_("drakbug --report drakconf &") }, undef, '' ], [ N("/_Help").N("/_About..."), undef, \&about_mdk_cc, undef, '' ] ); my ($menu, $factory) = create_factory_menu($window_global, @menu_items); # to retrieve a path, one must prevent "accelerators completion": sub get_path { join('', map { my $i = $_; $i =~ s/_//; $i } @_) } # menus do not like "_" from profiles (whereas netprofile does not like spaces in profile names...) sub profile2menu { my ($profile) = @_; $profile =~ s/_/ /g; "/$profile"; } sub enable_profile_entry { my ($profile, $value) = @_; eval { $factory->get_widget(get_path("
", $shared_translations{profiles} . profile2menu($profile)))->set_active($value); }; print "error: $@\n" if $@; } # update profiles list sub update_profiles { my ($removing) = @_; my $done; $factory->delete_item(get_path("
", $shared_translations{profiles} . profile2menu($_))) foreach @profiles; network::netconnect::read_net_conf($netcnx, $netc, $intf); @profiles = network::netconnect::get_profiles(); foreach my $prof (@profiles) { $factory->create_item([ $shared_translations{profiles} . profile2menu($prof), undef, sub { return unless $done; if ($netcnx->{PROFILE} eq $prof) { $done = 0; enable_profile_entry($prof, 1); $done = 1; return 0; } if (!warn_dialog(N("Warning"), N("We are about to switch from the \"%s\" profile to the \"%s\" profile. Are you sure you want to do the switch?", $netcnx->{PROFILE}, $prof), { transient => $window_global })) { $done = 0; enable_profile_entry($prof, 0); $done = 1; return; } # wait message is needed $done = 0; enable_profile_entry($netcnx->{PROFILE}, 0); $netcnx->{PROFILE} = $prof; network::netconnect::set_profile($netcnx); enable_profile_entry($prof, 1) if !$removing; $done = 1; }, undef, '' ]); # Radio } $factory->get_widget(get_path("
", $shared_translations{profiles}, $shared_translations{delete}))->set_sensitive(@profiles > 1); eval { $factory->get_widget(get_path("
", $shared_translations{profiles} . profile2menu($netcnx->{PROFILE})))->set_active(1) }; print "error is «$@»\n" if $@; $done = 1; } update_profiles(); %check_boxes = map { $_ => $factory->get_widget("
" . get_path(@{$options{$_}})) } ("show_log", if_(0 && $isWiz, "wiz_expert")); gtkadd($window_global, gtkpack_(Gtk2::VBox->new(0, 0), 0, $menu, # 0, gtkset_size_request(Gtk2::VBox->new(10, 10), -1, 2), 1, gtkpack_(Gtk2::VBox->new(0, 0), 0, my $banner_notebook = Gtk2::Notebook->new, 0, Gtk2::HSeparator->new, 1, my $notebook_global = gtkset_name(Gtk2::Notebook->new, 'mcc'), 1, gtkpack(my $emb_box = Gtk2::VBox->new(0, 0), my $wait_darea = gtkset_size_request(Gtk2::DrawingArea->new, -1, -1), ), 0, Gtk2::HSeparator->new, ), 0, my $buttons = gtkadd(gtkset_layout(Gtk2::HButtonBox->new, 'start'), map { gtkset_border_width($_, 3) } gtksignal_connect(my $cancel = Gtk2::Button->new(N("Cancel")), clicked => sub { stop_wait_area(); kill_children(); child_just_exited(); }), gtksignal_connect(my $previous = Gtk2::Button->new(N("Previous")), clicked => sub { $page_id = 0; warn_on_startup(); }, ), gtkset_sensitive(gtkset_relief(Gtk2::Button->new(""), 'none'), 0), ), ) ); sub notebook_set_background { my ($w) = @_; return if !$w->realized; my $color = $window_global->get_style->base('normal'); Gtk2::Rc->parse_string(join('', qq(style "mcc" { bg[NORMAL] = { ), join(' ,', $color->red, $color->green, $color->blue), qq( } bg_pixmap[NORMAL] = "" } widget "*mcc*" style "mcc" ))); $notebook_global->reset_rc_styles; }; $notebook_global->signal_connect(realize => \¬ebook_set_background); $window_global->signal_connect("style-set" => sub { notebook_set_background($notebook_global) }); $window_global->signal_connect(delete_event => \&quit_global); use POSIX qw(:sys_utsname_h :math_h :sys_wait_h :unistd_h); my (undef, $nodename) = POSIX::uname(); $window_global->set_title(N("Mandrakelinux Control Center %s [on %s]", $version, $nodename)); $window_global->set_position('center'); foreach my $notebook ($notebook_global, $banner_notebook) { $notebook->set_property('show-border', 0); $notebook->set_property('show-tabs', 0); } # banner : add2notebook($banner_notebook, "", Gtk2::Banner->new("/usr/share/icons/large/drakconf.png", N("Welcome to the Mandrakelinux Control Center"))); # main page (summary) : add2notebook($notebook_global, "", create_scrolled_window(gtkset_size_request(my $main_page = Gtk2::HBox->new, 50, 50), [ 'never', 'automatic' ], 'none', ), ); my ($hand_cursor, $normal_cursor, $wait_cursor) = map { Gtk2::Gdk::Cursor->new($_) } qw(hand2 left-ptr watch); my ($index, $left_locked) = (0, 0); my $spacing = 25; my @main_icons; foreach (@tree) { my ($text, $icon, $subtree) = @$_; my @subtree; foreach my $stuff (@$subtree) { my $exec = first(split /\s+/, $exec_hash->{$stuff->[0]}[1]); # do not complain about missing entries in move: if (-x $exec) { push @subtree, $stuff; } else { warn qq("$exec" is not executable) if $mdk_rel !~ /Move/; } } # Skip empty classes: next if !@subtree; my $my_index = $index++; my $box; $box = Gtk2::WebIcon->new($text, $icon, { button_release_event => sub { # FIXME: the following code is currently useless: # should we provide a way to kill buggy embedded programs ? return if $left_locked; $page_id = $my_index + 1; warn_on_startup(); }, } ); # Create right notebook pages : my $tbl = create_packtable({ col_spacings => $spacing, row_spacings => $spacing, homogeneous => 1, mcc => 1 }, group_by3(map { my ($label, $tag) = @$_; my $text = $exec_hash->{$label}[3]; die "$label 's icon is missing" if !$exec_hash->{$label} && $::testing; my $event_box; $event_box = Gtk2::WebIcon->new($text, $tag, { button_release_event => sub { compute_exec_string($label, $event_box, $tag, $text, @{$exec_hash->{$label}}) }, } ); $event_box->set_events([ 'enter_notify_mask', 'leave_notify_mask', 'button_press_mask', 'button_release_mask' ]); $tool_feedback{$label} = sub { $event_box->window->set_cursor($hand_cursor) }; $event_box; } @subtree)); add2notebook($notebook_global, "", my $_w_ret = create_scrolled_window(gtkset_border_width($tbl, 5), [ 'never', 'automatic' ], 'none', ), ); # $w_ret->vscrollbar->set_size_request(19, undef); push @main_icons, $box; add2notebook($banner_notebook, "", Gtk2::Banner->new($icon, $text)); } gtkadd($main_page, create_scrolled_window(create_packtable({ col_spacings => $spacing, row_spacings => $spacing, homogeneous => 1, mcc => 1 }, group_by3(@main_icons)), [ 'never', 'automatic' ], 'none', ) ); foreach (keys %check_boxes) { my $widget = $check_boxes{$_}; if (defined $widget) { $widget->set_active($option_values{$_}); } else { print STDERR qq(BUG with LANGUAGE "$ENV{LANGUAGE}" for "$_"\n); } }; # "wait while launching a program" area : my ($run_pixbuf, $run_counter, $run_counter_add, %run_pixbufs); sub stop_wait_area() { $wait_darea->hide; Glib::Source->remove($timeout) if $timeout; undef %run_pixbufs; } $wait_darea->signal_connect(expose_event => sub { my ($w) = @_; return if !$wait_darea->realized; return unless $run_pixbuf; # some people got an expose event before we start an embedded tool return unless $wait_darea->{size}; $wait_darea->{layout} ||= $wait_darea->create_pango_layout(N("Loading... Please wait")); my $pixbuf = $run_pixbufs{$run_counter} ||= render_alpha($run_pixbuf, $run_counter); if (!$wait_darea->{first_expose}) { $wait_darea->{first_expose} = 1; my $size = $wait_darea->{size}; my ($d_width, $d_height) = ($size->width, $size->height); ($w->{pix_width}, $w->{pix_height}) = ($pixbuf->get_width, $pixbuf->get_height); my ($txt_width, $txt_height) = $wait_darea->{layout}->get_pixel_size; $w->{pix_yy} = ($d_width - $w->{pix_width})/2; $w->{pix_xx} = ($d_height - $w->{pix_height} - $txt_height)/2; $w->{text_yy} = $w->{pix_yy} + ($w->{pix_width} - $txt_width)/2; $w->{text_xx} = $w->{pix_xx} + $w->{pix_height} + 5; } $pixbuf->render_to_drawable($w->window, $w->style->fg_gc('normal'), 0, 0, $w->{pix_yy}, $w->{pix_xx}, $w->{pix_width}, $w->{pix_height}, 'normal', 0, 0); my $style = $wait_darea->{style} ||= $wait_darea->get_style; $wait_darea->window->draw_layout($style->text_gc('normal'), $w->{text_yy}, $w->{text_xx}, $w->{layout}); $run_counter += $run_counter_add; $run_counter_add = -$run_counter_add if $run_counter < 100 || 255-$run_counter_add < $run_counter; }); gtkflush(); set_page_raw(0); $notebook_global->signal_connect(switch_page => sub { my $tab_number = $_[2]; return unless $tab_number > 0; }); $window_global->show_all; show_hide_previous(0); $emb_box->hide; $wait_darea->realize; $SIG{USR1} = 'IGNORE'; $SIG{USR2} = 'IGNORE'; $SIG{TERM} = \&quit_global; $SIG{CHLD} = \&sig_child; $window_splash->destroy; undef $window_splash; Glib::Timeout->add(200, sub { sig_child(1); 1 }); Gtk2->main; sub group_by3 { my @l; for (my $i = 0; $i < @_; $i += 3) { push @l, [ $_[$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(); } set_page($page_id); } #------------------------------------------------------------- # 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; show_hide_previous(1); $cancel->hide; gtkset_mousecursor_normal(); foreach my $notebook ($previous, $notebook_global, $banner_notebook) { $notebook->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__(Gtk2::VBox->new(0, 0), Gtk2::Banner->new($icon, $label), Gtk2::HSeparator->new, ), 1, gtksignal_connect($emb_socket = Gtk2::Socket->new, 'plug-removed' => sub { $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; $banner->show; stop_wait_area(); $left_locked = 0; show_hide_previous(0); $buttons->hide; return if !$emb_socket; $emb_socket->show; $emb_socket->can_focus(1); $emb_socket->grab_focus; #$emb_socket->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 compute_exec_string { my ($label, $box, $icon, $text, $_log_exp, $exec_, $gtkplug, undef, $alternate) = @_; #($_[0], @{$_[1]}); return if $tool_pids{$label}; my $exec = ref($exec_) ? $exec_->[0] : $exec_; if (! -x first(split /\s+/, $exec)) { 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) { foreach my $notebook ($notebook_global) { $notebook->hide; } create_hidden_socket($icon, $text); $emb_box->show; $emb_socket->realize; $pending_app = 1; if ($gtkplug > 0) { $exec .= " --embedded " . $emb_socket->get_id; $wait_darea->show; $cancel->show; $previous->hide; $run_pixbuf = gtkcreate_pixbuf($icon . "_128"); $run_counter = 255; $run_counter_add = -5; undef $wait_darea->{color}; undef $wait_darea->{size}; undef $wait_darea->{first_expose}; $wait_darea->{not_yet_complete} = 1; $timeout = Glib::Timeout->add(35, sub { my $size = $wait_darea->{size} ||= $wait_darea->allocation; my ($width, $height) = ($size->width, $size->height); my $style = $wait_darea->{style} ||= $wait_darea->get_style; my $bg = $wait_darea->{bg} ||= $style->bg('normal'); my $base = $wait_darea->{base} ||= $style->base('normal'); my $color = $wait_darea->{color} ||= $base; # $base -> $bg my @colors = ([ $color->red, $bg->red ], [ $color->green, $bg->green ], [ $color->blue, $bg->blue ]); if ($wait_darea->{not_yet_complete} && any { $_->[0] != $_->[1] } @colors) { $wait_darea->window->set_background($color); $wait_darea->{color} = gtkcolor(map { $_->[0] - min(300, $_->[0] - $_->[1]) } @colors); $wait_darea->queue_draw; } elsif ($wait_darea->{not_yet_complete}) { # set exact color $wait_darea->window->set_background($bg); $wait_darea->queue_draw; $wait_darea->{not_yet_complete} = 0; } else { my $w = $wait_darea; $wait_darea->queue_draw_area($w->{pix_yy}, $w->{pix_xx}, $w->{pix_width}, $w->{pix_height}); } 1; }); $left_locked = 1; $tool_pids{$label} = fork_($exec); } else { # gtkplug == 0 $emb_box->grab_focus; $emb_socket->grab_focus; $emb_socket->show; $exec_->[0] = $exec; $SIG{CHLD} = undef; $emb_socket->add_id(launch_xapp(@$exec_)); $SIG{CHLD} = \&sig_child; } } else { # not embedded # fix #3415 when $gtkplug eq -1 my $old = $option_values{embedded}; $option_values{embedded} = 0; $tool_pids{$label} = fork_($gtkplug == 0 ? $exec_->[0] : $alternate || $exec); $option_values{embedded} = $old; } start_logdrake(); $box->window->set_cursor($wait_cursor); } 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 launch_xapp { my ($exec, $name, $xx) = @_; my $find_windows = sub { local *X; open(X, "-|", "xwininfo -root -tree -int"); grep { /$name/ } ; }; my @before = &$find_windows(); fork_($exec); my @after = &$find_windows(); require Time::HiRes; while (@after != $xx + @before) { Time::HiRes::usleep(50); @after = &$find_windows() } my $c = top(difference2(\@after, \@before)); return $1 if $c =~ /\s*([0-9]*)\s*/; } sub kill_them_all { map { if__($_, kill 'TERM', $_) } @_; } sub kill_children() { kill_them_all(@pid_launched); @pid_launched = (); } sub kill_logdrake() { kill_them_all($pid_exp) if $pid_exp; } sub quit_global() { &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, THEME => $theme, }); gtkset_mousecursor_normal(); Gtk2::exit(0); } #------------------------------------------------------------- # signals managment # got when child died and gone in zombie state sub sig_child() { my ($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}; } 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(Gtk2::Dialog->new, 10); $dialog->set_transient_for($window_global); $dialog->set_position('center-on-parent'); $dialog->set_title($title); $dialog->action_area->pack_start(gtkadd(Gtk2::HButtonBox->new, gtksignal_connect(Gtk2::Button->new(N("Close")), clicked => sub { $dialog->destroy }) ), 0,0,0) unless $o_no_button; gtkset_modal($dialog, 1); } sub more_themes() { my $window_about = new_dialog(N("More themes")); gtkpack_($window_about->vbox, 0, Gtk2::Label->new(N("Getting new themes")), 0, gtkadd(gtkset_shadow_type(gtkset_border_width(Gtk2::Frame->new(N("Additional themes")), 10), 'etched_out'), gtkpack(Gtk2::HBox->new(0, 5), N("Get additional themes on www.damz.net"), ) ) ); $window_about->show_all; } sub about_mdk_cc() { my $window_about = new_dialog(N("About - Mandrakelinux Control Center")); my $tree_model = Gtk2::TreeStore->new("Glib::String", "Glib::String", "Glib::String"); my $credits_model = Gtk2::TreeStore->new("Glib::String", "Glib::String"); my ($list, $clist) = (Gtk2::TreeView->new_with_model($tree_model), Gtk2::TreeView->new_with_model($credits_model)); $_->can_focus(0) foreach $list, $clist; each_index { $list->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => $::i)) } 0..2; each_index { $clist->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => $::i)) } 0..1; $_->set_headers_visible(0) foreach $list, $clist; foreach my $row ([ N("Authors: "), '', '' ], [ '', 'Chmouel Boudjnah', N("(original C version)") ], [ '', 'Damien "dam\'s" Krotkine', #-PO "perl" here is the programming language N("(perl version)") ], [ '', 'Daouda Lo', '' ], [ '', 'Thierry Vignaud', '' ], [ '', 'Yves Duret', N("(perl version)") ], [ '', '' ], [ N("Artwork: "), '', '' ], [ '', 'Anh-Van Nguyen', N("(design)") ], [ '', #-PO If your language allows it, use eacute for first "e" and egrave for 2nd one. N("Helene Durosini"), '' ], ) { $tree_model->append_set(undef, [ map_index { $::i => $_ } @$row ]); } foreach my $line (grep { $_ ne "\n" } cat_(top(glob("/usr/share/doc/mandrake-release-*/CREDITS")))) { if ($line =~ /^\s*\* (.*)$/) { my ($contributor, $descr) = split(', ', $1, 2); chomp($descr); $descr =~ s/(["\@\$])/\\$1/g; $credits_model->append_set(undef, map_index { $::i => $_ } map { join("\n", warp_text(translate(common::sprintf_fixutf8($_)), 30)) } $contributor, $descr); } else { chomp($line); $credits_model->append_set(undef, 0 => translate(common::sprintf_fixutf8($line))); } } # 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("~ * ~"); #-PO Add your E-Mail address here if you want to show it in the about doialog. my $translator_email = N("~ @ ~"); if ($translator_name ne "~ * ~ " && 0) { $list->append_set(undef, [ 0 => $_->[0], 1 => $_->[1] ]) foreach [ '', '' ], [ N("Translator: "), $translator_name, $translator_email ]; } $list->get_selection->set_mode('none'); my $_unused_string = N("Mandrakelinux Control Center %s\n", $version) . N("About - Mandrakelinux Control Center"); gtkpack_($window_about->vbox, (0, gtkset_size_request(Gtk2::Banner->new('splash_screen_about', N("Mandrakelinux Control Center"), { txt_ypos => 50 }), -1, 120)), 0, Gtk2::Label->new(""), 0, Gtk2::Label->new(N("Copyright (C) 1999-2004 Mandrakesoft SA") . "\n"), 0, Gtk2::HSeparator->new, 0, Gtk2::Label->new(""), 1, my $n = Gtk2::Notebook->new, ); add2notebook($n, N("Authors"), $list); add2notebook($n, N("Mandrakelinux Contributors"), create_scrolled_window(gtkset_size_request($clist, 50, 50))); $window_about->show_all; } #------------------------------------------------------------- # mcc specific graphic functions: sub set_page_raw { my ($bool) = @_; foreach my $notebook ($notebook_global, $banner_notebook) { $notebook->set_current_page($bool); } } sub set_page { my ($index) = @_; start_logdrake(); set_page_raw($index); $help_on_context = $ctx[$index]; show_hide_previous($index); } sub show_hide_previous { my ($bool) = @_; if ($bool) { $previous->show; $buttons->show; } else { $previous->hide } $cancel->hide; } #------------------------------------------------------------- # mcc specific graphic functions: sub rtl_gtkcreate_pixbuf { my ($icon) = @_; my $pixbuf; eval { $pixbuf = ugtk2::gtkcreate_pixbuf($icon . "_rtl") } if lang::text_direction_rtl(); $pixbuf ||= ugtk2::gtkcreate_pixbuf($icon); } sub rtl_gtkcreate_img { my ($icon) = @_; lang::text_direction_rtl() ? ugtk2::gtkcreate_img($icon . "_rtl") : ugtk2::gtkcreate_img($icon); } sub new_pixbuf { my ($pixbuf) = @_; my ($height, $width) = ($pixbuf->get_height, $pixbuf->get_width); my $new_pixbuf = Gtk2::Gdk::Pixbuf->new('rgb', 1, 8, $height, $width); $new_pixbuf->fill(0x00000000); # transparent white $width, $height, $new_pixbuf; } sub render_alpha { my ($pixbuf, $alpha_threshold) = @_; my ($width, $height, $new_pixbuf) = new_pixbuf($pixbuf); $pixbuf->composite($new_pixbuf, 0, 0, $width, $height, 0, 0, 1, 1, 'nearest', $alpha_threshold); $new_pixbuf; } sub render_shiner { my ($pixbuf, $shine_value) = @_; my $new_pixbuf = (new_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'); } package Gtk2::WebIcon; use ugtk2 qw(:helpers :wrappers); use common; sub new { my ($_class, $text, $icon, $callbacks) = @_; my $hbox_spacing = 10; # FIXME: do ->set_pixbuf() on {enter,leave}_events my $box = gtkadd(gtksignal_connect(Gtk2::EventBox->new, realize => sub { $_[0]->window->set_cursor($hand_cursor) }), gtkpack_( # better VBox of StockButtons there (with IconFactory) Gtk2::VBox->new(0, $hbox_spacing), 0, gtkcreate_img($icon), 1, gtktext_insert( # disable selecting text and popping the contextual menu # (GUI team says it's *horrible* to be able to do select text!) gtksignal_connect(my $tv = Gtk2::TextView->new, button_press_event => sub { 1 }), [ [ $text, {'background_set' => 0, justification => "center", 'background_stipple_set' => 0 } ] ]) ), ); $tv->set_events([]); # FIXME : resize sig: ->foreach; set_size_request $callbacks->{key_press_event} = sub { my (undef, $event) = @_; return if !$callbacks->{button_release_event}; $callbacks->{button_release_event}->() if $Gtk2::Gdk::Keysyms{Return} eq $event->keyval; }; while (my ($signal, $handler) = each %$callbacks) { $box->signal_connect($signal => $handler); } gtkset_size_request($box, 50, -1); $box->set_events([ 'enter_notify_mask', 'leave_notify_mask', 'button_press_mask', 'button_release_mask', 'key-press-mask' ]); return gtkset_border_width($box, 10); } 1;