+MAIN: while (1) {
+ if (defined $current) {
+ my $go = show_slide ($current);
+ if (defined $go) {
+ print STDERR "go = $go\n" if $verbose;
+ last MAIN if $go eq "QUIT";
+
+ my $i = $current->{i};
+ print STDERR "i = $i\n" if $verbose;
+ $i-- if $go eq "PREV" && $i > 0;
+ $i++ if $go eq "NEXT" && $i+1 < @files;
+ $i = 0 if $go eq "FIRST";
+ $i = $#files if $go eq "LAST";
+ $i = $1 if $go =~ /^I_(\d+)$/;
+ $current = $files[$i];
+ }
+ } else {
+ print "No slides found. Press any key to reload directory ...\n";
+ $_ = <STDIN>;
+ }
+
+ # Reread directory between slides.
+ reread_directory ();
+
+ if (defined $current && !exists $files{$current->{name}}) {
+ # Current slide was deleted.
+ undef $current;
+ $current = $files[0] if @files;
+ }
+}
+
+sub show_slide
+{
+ my $slide = shift;
+
+ # Display an HTML page.
+ if ($slide->{ext} eq "html") {
+ # MozEmbed is incredibly crashy, so we run ourself as a
+ # subprocess, so when it segfaults we don't care. If all goes
+ # well and it doesn't crash, it should print a line 'RESULT FOO'
+ # where 'FOO' is the instruction (eg. 'NEXT', 'PREV', 'QUIT' etc).
+ my @cmd = ($0, "--mozembed", $talkdir, $slide->{name});
+ print STDERR "running subcommand: ", join (" ", @cmd), "\n"
+ if $verbose;
+ open CMD, "-|", @cmd
+ or die "failed to execute subcommand: ", join(" ", @cmd), ": $!\n";
+ my $r;
+ while (<CMD>) {
+ if (/^RESULT ([A-Z]+.*)/) {
+ $r = $1;
+ print STDERR "subcommand result: $r\n" if $verbose;
+ last;
+ }
+ }
+ # No RESULT line? Subcommand probably segfaulted, just
+ # continue to next slide.
+ $r ||= "NEXT";
+ return $r;
+ }
+ # Run a shell command.
+ elsif ($slide->{ext} eq "sh") {
+ my $pid;
+ # http://docstore.mik.ua/orelly/perl/cookbook/ch10_17.htm
+ local *run_process = sub {
+ $pid = fork ();
+ die "fork: $!" unless defined $pid;
+ unless ($pid) {
+ # Child.
+ POSIX::setsid ();
+ exec ("./".$slide->{name});
+ die "failed to execute command: ", $slide->{name}, ": $!";
+ }
+ # Parent returns.
+ };
+ local *kill_process = sub {
+ print STDERR "sending TERM signal to process group $pid\n"
+ if $verbose;
+ kill "TERM", -$pid;
+ };
+ run_process ();
+
+ my $r = "NEXT";
+
+ my $w = Gtk2::Window->new ();
+
+ my $s = $w->get_screen;
+ $w->set_default_size ($s->get_width, -1);
+ $w->move (0, 0);
+ $w->set_decorated (0);
+
+ my $bbox =
+ make_button_bar ((exists $slide->{first}),
+ (exists $slide->{last}),
+ sub { $r = $_[0]; $w->destroy },
+ restart => sub {
+ kill_process ();
+ run_process ();
+ },
+ );
+
+ $w->add ($bbox);
+
+ $w->signal_connect (destroy => sub {
+ Gtk2->main_quit;
+ return FALSE;
+ });
+ $w->show_all ();
+
+ Gtk2->main;
+
+ kill_process ();
+ print STDERR "returning r=$r\n" if $verbose;
+ return $r;
+ }
+}
+
+# If invoked with the --mozembed parameter then we just display a
+# single page. This is just to prevent crashes in MozEmbed from
+# killing the whole program.
+sub run_mozembed
+{
+ my $w = Gtk2::Window->new ();
+ my $vbox = Gtk2::VBox->new ();
+ my $moz = Gtk2::MozEmbed->new ();
+
+ reread_directory ();
+
+ my $name = $ARGV[1];
+ $current = $files{$name};
+ my $url = "file://$talkdir/$name";
+
+ my $bbox =
+ make_button_bar ($current->{first}, $current->{last},
+ sub { print "RESULT ", $_[0], "\n"; $w->destroy }
+ );
+
+ $vbox->pack_start ($bbox, 0, 0, 0);
+ $vbox->add ($moz);
+ $w->fullscreen ();
+ #$w->set_default_size (640, 480);
+ $w->add ($vbox);
+
+ $w->signal_connect (destroy => sub {
+ Gtk2->main_quit;
+ return FALSE;
+ });
+ $w->show_all ();
+
+ $moz->load_url ($url);
+
+ Gtk2->main;
+
+ exit 0;
+}
+
+# Make the standard button bar across the top of the page.
+sub make_button_bar
+{
+ my $first = shift;
+ my $last = shift;
+ my $cb = shift;
+ my %params = @_;
+
+ my $bbox = Gtk2::Toolbar->new ();
+ $bbox->set_style ("GTK_TOOLBAR_TEXT");
+
+ my $i = 0;
+
+ my $bnext = Gtk2::ToolButton->new (undef, "Next slide");
+ $bnext->signal_connect (clicked => sub { &$cb ("NEXT") });
+ $bnext->set_sensitive (!$last);
+ $bbox->insert ($bnext, $i++);
+
+ my $bback = Gtk2::ToolButton->new (undef, "Back");
+ $bback->signal_connect (clicked => sub { &$cb ("PREV") });
+ $bback->set_sensitive (!$first);
+ $bbox->insert ($bback, $i++);
+
+ if (exists $params{restart}) {
+ $bbox->insert (Gtk2::SeparatorToolItem->new (), $i++);
+
+ my $brestart = Gtk2::ToolButton->new (undef, "Kill & restart");
+ $brestart->signal_connect (clicked => $params{restart});
+ $bbox->insert ($brestart, $i++);
+ }
+
+ my $sep = Gtk2::SeparatorToolItem->new ();
+ $sep->set_expand (TRUE);
+ $sep->set_draw (FALSE);
+ $bbox->insert ($sep, $i++);
+
+ my $optsmenu = Gtk2::Menu->new ();
+
+ my $bfirst = Gtk2::MenuItem->new ("First slide");
+ $bfirst->signal_connect (activate => sub { \&$cb ("FIRST") });
+ $bfirst->show ();
+ $optsmenu->append ($bfirst);
+
+ my $blast = Gtk2::MenuItem->new ("Last slide");
+ $blast->signal_connect (activate => sub { \&$cb ("LAST") });
+ $blast->show ();
+ $optsmenu->append ($blast);
+
+ my $slidesmenu = Gtk2::Menu->new ();
+ foreach (@files) {
+ my $item = Gtk2::MenuItem->new ($_->{name});
+ my $index = $_->{i};
+ $item->signal_connect (activate => sub { \&$cb ("I_$index") });
+ $item->set_sensitive ($current->{i} != $index);
+ $item->show ();
+ $slidesmenu->append ($item);
+ }
+
+ my $bslides = Gtk2::MenuItem->new ("Slides");
+ $bslides->set_submenu ($slidesmenu);
+ $bslides->show ();
+ $optsmenu->append ($bslides);
+
+ my $sep2 = Gtk2::SeparatorMenuItem->new ();
+ $sep2->show ();
+ $optsmenu->append ($sep2);