#!/usr/bin/perl
# gui end of alertservice dbus service
# launches alertservice which sits on the dbus and waits for events
# gui launches various tasks, based on what alertservice echos out
# currently only works with hotlplug/usb.agent, and generic echo messages
# could tie into other things - msec, security updates?
# Stew Benedict <sbenedict@mandrakesoft.com>

use lib qw(/usr/lib/libDrakX);
use standalone;
use interactive;
use common;
use any;
use strict;

use Gtk2::TrayIcon;

eval { require ugtk2; ugtk2->import(qw(:all)); require Gtk2::Pango; };
if ($@) {
    print "This program cannot be run in console mode.\n";
    c::_exit(0);  #- skip ugtk2::END
}                   

my ($eventbox, $img);
my ($menu, $timeout, $big_timeout);
my $raisedwindow = 0;
add_icon_path("/usr/share/alert_applet/");
my $prog_name = "./alert_applet";
my $command = "/usr/bin/alertservice";
my $message;
# menu launch has $DISPLAY=:N, shell has :N.N
my $display = $ENV{DISPLAY};
$display .= ".0" if length($display) == 2; 
my $session_file = "/var/tmp/dbus-xsession$display";
my $tmpcron = "$ENV{HOME}/tmp/crontab.tmp";
my $childpid2;
my @pids;
my $in = 'interactive'->vnew();
my $rcfile = "$ENV{HOME}/.alert_appletrc";

#check if systray enabled wm is running
my $wm = any::running_window_manager();
if (!member($wm, 'xfce', 'kwin', 'gnome-session')) {
	$in->ask_warn(N("Alert Applet"), N("Not a system tray enabled Window Manager"));
	exit 1;
}

# see if we have a current dbus session going
# make sure it's not running, with no or bad environment defined
my %dbus = getVarsFromSh($session_file) if -f $session_file;
if (defined $dbus{DBUS_SESSION_BUS_PID} && -d "/proc/$dbus{DBUS_SESSION_BUS_PID}") {
	log::explanations("DBus session already running at pid $dbus{DBUS_SESSION_BUS_PID}");
	$dbus{DBUS_SESSION_BUS_ADDRESS} =~ s|'||g;
	$ENV{DBUS_SESSION_BUS_ADDRESS} = $dbus{DBUS_SESSION_BUS_ADDRESS};
} else {	
	log::explanations("Starting a DBus session");	
	my @values = `dbus-launch --sh-syntax --exit-with-session`;             
	push(@values, "export DBUS_SESSION_BUS_PID\n");
	output($session_file, @values);
	chomp $values[0];
	$values[0] =~ s|DBUS_SESSION_BUS_ADDRESS=||;
	$values[0] =~ s|'||g;
	$ENV{DBUS_SESSION_BUS_ADDRESS}=$values[0];
	chomp $values[2];
	$values[2] =~ s|DBUS_SESSION_BUS_PID=||;
	$dbus{DBUS_SESSION_BUS_PID} = $values[2];	
}

# see if we have drakalert running as this user
@pids = fuzzy_pidofs(qr/\balertservice\b/);
foreach (@pids) {
	my $uid = `grep Uid /proc/$_/status`;
	if ($uid =~ /$</) {
 		log::explanations("$command already running for user $ENV{USER}");
		$in->ask_warn(N("Alert Applet"), N("%s already running for user %s\n", $command, $ENV{USER})); 
		exit 1;
	}
}

# read rc file
my %conf = getVarsFromSh($rcfile) if -f $rcfile; 

my %appletstate = (
	alert => {
		colour => [ 'alert2' ],
		menu => [ 'clear', 'help', 'about' ],
		tt => [ N("") ]
	},
	device => {
		colour => [ 'hwinfo2' ],
		menu => [ 'clear', 'download', 'help', 'about' ],
		tt => [ N("New device, click on \"Clear\" or \"Download\"") ]
	},
    noalert => {
		colour => [ 'alert_applet' ],
		menu => [ 'help', 'about' ],
		tt => [ N("No alerts") ]
    }
);

my %actions = (
	'clear' => { name => N("Clear Alert"), launch => sub { clearAlert() } },
	'download' => { name => N("Download Driver"), launch => sub { getDriver() } },
	'help' => { name => N("Get Online Help"), launch => sub { showAbout() } },
    'about' => { name => N("About"), launch => sub { showAbout() } }
);

gtkadd(my $icon = Gtk2::TrayIcon->new("Alert_Applet"),
	gtkadd($eventbox = Gtk2::EventBox->new,
		gtkpack($img = Gtk2::Image->new)
    )
);

$eventbox->signal_connect(button_press_event => sub {
	if (!$raisedwindow) {
		if ($_[1]->button == 1) {
			$raisedwindow = 1; 
			# can launch some app here, just do "about" for now
			showAbout(); 
		}
	}
	$_[1]->button == 3 && $menu and $menu->popup(undef, undef, undef, undef, $_[1]->button, $_[1]->time);
});

go2State('noalert', '');
cronDBus();

$icon->show_all;

pipe(INPUT, OUTPUT);
my $childpid = fork();
if ($childpid != 0) {
	#parent process
	close (OUTPUT);
	Gtk2->main;
	ugtk2::exit(0);
} else {
	#child process - forking seemed to be the only way to keep Gtk2->main and dbus monitoring both going
	my $value;
	close (INPUT);
	local *SERVICE;
	log::explanations("Starting alertservice dbus service");
	$childpid2 = open SERVICE, "$command 2>&1 |";
	select OUTPUT;
	$| = 1;
	while ($value = <SERVICE>) {
		if ($value =~ /^Mandriva_echo_service_message/) {
			chomp $value;
			my @service_args = split(' ', $value);
			shift(@service_args);
			# just pass the message on to the parent process
			print "@service_args\n";
		}
	}
	close SERVICE;
	exit;
}

sub launchBrowser {
	my ($ancpath) = @_;
	# taken from drakhelp
	my $wm = any::running_window_manager();
	my %launchhelp = (
		'kwin' => sub { system("konqueror " . $ancpath . "&") },
        'gnome-session' => sub { system("yelp ghelp://" . $ancpath . "&") },
        'other' => sub { my $browser = $ENV{BROWSER} || find { -x "/usr/bin/$_" } qw(mozilla konqueror galeon) or $in->ask_warn('drakhelp', N("No browser is installed on your system, Please install one if you want to browse the help system"));
        	log::explanations("Launching browser to: $ancpath");
        	system("$browser " . $ancpath . "&") 
	});
	member($wm, 'kwin', 'gnome-session') or $wm = 'other';
	eval { $launchhelp{$wm}->() };
}

sub showAbout {
	nocronDBus();
	$in->ask_warn(N("Alert Applet"), N(" alert_applet, Copyright GPL 2004\n Stew Benedict <sbenedict\@mandrakesoft.com>"));
	gtkset_mousecursor_normal();
	cronDBus();
}

sub getDriver {
	# should we perhaps append a kernel version to the url (using uname -r) to get the correct version for the running kernel?
	# probably want to also install this driver, via gurpmi, maybe skip the browser altogether?
	my @url = split(" ", $message);	
	launchBrowser($url[1]) if $url[1];
	clearAlert();
}

sub clearAlert {
	go2State('noalert', '');
	cronDBus();
}

sub checkDBus {
	# this was blocking, preventing the GUI from refreshing
	# check first if there's anything to read (from the Perl Cookbook)	
	my $rin = '';
	vec($rin, fileno(INPUT), 1) = 1;
	my $rout = '';
	my $nfound = select($rout=$rin, undef, undef, 0);
	if ($nfound) {
		$message = <INPUT>;
		if ($message ne '') {
			mainQuit() if $message =~ /Failed to connect/;
			nocronDBus();
			if ($message =~ /^NewDevice/) {
				go2State('device', $message) 
			} elsif ($message =~ /^Updates/) {
				go2State('update', $message)
			} else { 
				go2State('alert', $message);
			}
		} else {
			go2State('noalert', '');
		}
	}
}

sub cronDBus {
	$timeout = Glib::Timeout->add(10*1000, sub {
		checkDBus();
        1;
	});
}

sub nocronDBus {
	#turn off dbus monitoring when an alert is pending
	Glib::Source->remove($timeout);
}

sub go2State {
    $menu && $menu->destroy;
    $menu = setState(@_);
	gtkflush();
}

sub toggleIcon {
	($conf{NO_ICON}) = @_;
	go2State('noalert', '');
}

sub toggleSound {
	($conf{USE_SOUND}) = @_;
	go2State('noalert', '');
}

sub setSound {
	my ($prompt) = @_; 
	my $file_dialog;
	
    $file_dialog = gtksignal_connect(new Gtk2::FileSelection($prompt), destroy => sub { $file_dialog->destroy });
	$file_dialog->set_filename($conf{SOUND_FILE}) if $conf{SOUND_FILE};
    $file_dialog->ok_button->signal_connect(clicked => sub { 
    	my $filename = $file_dialog->get_filename;
		$conf{SOUND_FILE} = $filename if $filename ne '';
 		$file_dialog->destroy;
	});
    $file_dialog->cancel_button->signal_connect(clicked => sub { $file_dialog->destroy });
    $file_dialog->show;
}

sub setState {
    my ($state_type, $amessage) = @_;
    my $arr = $appletstate{$state_type}{menu};
	if ($conf{NO_ICON} && $state_type eq "noalert") {
		$img->set_from_pixbuf(undef);
	} else {    
		my $tmp = gtkcreate_pixbuf($appletstate{$state_type}{colour}->[0]);
		$img->set_from_pixbuf($tmp);
	}
	`/usr/bin/alert_sound $conf{SOUND_FILE}` if ($conf{USE_SOUND} && $state_type eq "alert" && $conf{SOUND_FILE});
    gtkset_tip(new Gtk2::Tooltips, $eventbox, formatAlaTeX($appletstate{$state_type}{tt}->[0] . "\n" . $amessage));
    my $menu = Gtk2::Menu->new;
    foreach (@$arr) {
        my $l = $actions{$_}{name};
        $menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label($actions{$_}{name})), activate => $actions{$_}{launch}));
    }
	$menu->append(gtkshow(Gtk2::SeparatorMenuItem->new));
	if ($conf{NO_ICON}) {
		$menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("Show Icon"))), activate => sub { toggleIcon(0) } ));
	} else {
		$menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("Hide Icon"))), activate => sub { toggleIcon(1) } ));
    }
	if ($conf{USE_SOUND}) {
		$menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("No Sound"))), activate => sub { toggleSound(0) } ));
		$menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("Sound File"))), activate => sub { setSound(N("Path to sound file?")) } ));

	} else {
		$menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("Use Sound"))), activate => sub { toggleSound(1) } ));
    }
    $menu->append(gtkshow(Gtk2::SeparatorMenuItem->new));
    $menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("Quit"))), activate => sub { mainQuit() }));
    $menu
}

sub mainQuit() {
	setVarsInSh($rcfile, \%conf);
	log::explanations("Terminating child process: $childpid");
	kill 15, $childpid;
	log::explanations("Terminating dbus-session: $dbus{DBUS_SESSION_BUS_PID}");
	kill 15, $dbus{DBUS_SESSION_BUS_PID};
	rm_rf($session_file) if -e $session_file;
	Gtk2->main_quit;
}
