Version 1.2.0 (stable).
[techtalk-pse.git] / techtalk-pse.pl
index 53b474f..b495911 100755 (executable)
@@ -3,7 +3,7 @@
 # @configure_input@
 #
 # Tech Talk PSE
-# Copyright (C) 2010 Red Hat Inc.
+# Copyright (C) 2010-2012 Red Hat Inc.
 #
 # 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
@@ -28,10 +28,11 @@ use Pod::Usage;
 use Getopt::Long;
 use Cwd qw(getcwd abs_path);
 use Glib qw(TRUE FALSE);
-use Gtk2 -init;
-use Gtk2::Gdk::Keysyms;
-use Gtk2::WebKit;
-use Gnome2::Vte;
+use Glib::Object::Introspection;
+use Gtk3 -init;
+use Gtk3::WebKit;
+
+Glib::Object::Introspection->setup(basename => "Vte", version => "2.91", package => "Vte");
 
 =encoding utf8
 
@@ -69,10 +70,26 @@ there is a discussion on L<WHAT MAKES A GOOD TALK>.
 
 =head1 RUNNING THE TOOL FROM THE COMMAND LINE
 
-A Tech Talk PSE talk is not a single file, but a directory full of
-files.  (If you want to start a new talk, see the L</TUTORIAL> section
-below).  To display or run the talk, change into the directory
-containing all those files and run the C<techtalk-pse> command:
+Tech Talk PSE talks are just directories containing C<*.html>,
+C<*.sh> (shell script) and C<*.term> (terminal) files:
+
+ 0010-introduction.html
+ 0500-demonstration.sh
+ 0600-command-line.term
+ 9900-conclusion.html
+
+The filenames that Tech Talk PSE considers to be slides have to match
+the regular expression:
+
+ ^(\d+)(?:-.*)\.(html|sh|term)$
+
+(any other file or subdirectory is ignored).  Shell scripts and
+terminal files I<must> be executable.
+
+=head2 DISPLAYING AN EXISTING TALK
+
+To display or run a talk, change into the directory containing all
+those files and run the C<techtalk-pse> command:
 
  cd /path/to/talk/; techtalk-pse
 
@@ -183,6 +200,9 @@ my %files;
 my $current;
 my $pid;
 my $pipeline;
+my $fullscreen = 1;
+my $width;
+my $height;
 
 &reread_directory ();
 
@@ -191,18 +211,20 @@ if (@files == 0) {
     warn "techtalk-pse: no files found, continuing anyway ...\n"
 }
 
-my $w = Gtk2::Window->new ();
-my $vbox = Gtk2::VBox->new ();
-my $webkit = Gtk2::WebKit::WebView->new ();
-my $vte = Gnome2::Vte::Terminal->new ();
-my $notebook = Gtk2::Notebook->new ();
+my $w = Gtk3::Window->new ("toplevel");
+my $vbox = Gtk3::VBox->new ();
+my $webkit = Gtk3::WebKit::WebView->new ();
+my $vte = Vte::Terminal->new ();
+my $notebook = Gtk3::Notebook->new ();
 my $splash = make_splash_page ();
+my $emptylabel = Gtk3::Label->new ();
 
-my $webkitscroll = Gtk2::ScrolledWindow->new ();
+my $webkitscroll = Gtk3::ScrolledWindow->new();
 $webkitscroll->add ($webkit);
 $webkitscroll->set_policy('automatic', 'automatic');
 
 my $webkitpage = $notebook->append_page ($webkitscroll);
+my $shpage = $notebook->append_page ($emptylabel);
 my $vtepage = $notebook->append_page ($vte);
 my $splashpage = $notebook->append_page ($splash);
 
@@ -223,8 +245,7 @@ $notebook->set_show_border(0);
 # NB careful setting it too big, because it will
 # force a min size on the terminal. Scaling 1.3
 # is biggest we can do while fitting 1024x768
-my $font = $vte->get_font;
-$font->set_size($font->get_size * 1.3);
+$vte->set_font_scale(1.3);
 
 # When an external command exits, automatically
 # go to the next slide
@@ -239,10 +260,20 @@ $vte->signal_connect (
 # Exit if the window is closed
 $w->signal_connect (
     destroy => sub {
-       Gtk2->main_quit;
+       Gtk3::main_quit;
        return FALSE;
     });
 
+$w->signal_connect (
+    'window-state-event' => sub {
+       if (!$fullscreen) {
+           $w->resize ($width, $height);
+           $w->move (500, 500);
+       }
+        return FALSE;
+    });
+
+
 # Handle left/right arrows, page up/down & home/end
 # as slide navigation commands. But not when there
 # is a shell running
@@ -256,23 +287,23 @@ $w->signal_connect (
            return 0;
        }
 
-       if ($ev->keyval == $Gtk2::Gdk::Keysyms{Right} ||
-           $ev->keyval == $Gtk2::Gdk::Keysyms{Page_Down}) {
+       if ($ev->keyval == &Gtk3::Gdk::KEY_Right ||
+           $ev->keyval == &Gtk3::Gdk::KEY_Page_Down) {
            &switch_slide("NEXT");
            return 1;
-       } elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{Left} ||
-                $ev->keyval == $Gtk2::Gdk::Keysyms{Page_Up}) {
+       } elsif ($ev->keyval == &Gtk3::Gdk::KEY_Left ||
+                $ev->keyval == &Gtk3::Gdk::KEY_Page_Up) {
            &switch_slide("PREV");
            return 1;
-       } elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{Home}) {
+       } elsif ($ev->keyval == &Gtk3::Gdk::KEY_Home) {
            &switch_slide("FIRST");
            return 1;
-       } elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{End}) {
+       } elsif ($ev->keyval == &Gtk3::Gdk::KEY_End) {
            &switch_slide("LAST");
            return 1;
-       } elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{q} ||
-                $ev->keyval == $Gtk2::Gdk::Keysyms{Escape}) {
-           Gtk2->main_quit;
+       } elsif ($ev->keyval == &Gtk3::Gdk::KEY_q ||
+                $ev->keyval == &Gtk3::Gdk::KEY_Escape) {
+           Gtk3::main_quit;
            return 1;
        }
        return 0;
@@ -280,17 +311,13 @@ $w->signal_connect (
 
 
 $w->add ($vbox);
-$w->show_all ();
-
-$w->set_decorated (0);
-$w->fullscreen ();
-$w->move (0,0);
 
-my $scr = $w->get_screen();
+$w->show_all ();
+window_fullscreen ();
 
 &update_slide();
 
-Gtk2->main();
+Gtk3::main();
 
 exit 0;
 
@@ -300,13 +327,13 @@ sub reread_directory
 
     my $i = 0;
     foreach (glob ("*")) {
-        if (/^(\d+)(?:-.*)\.(html|sh)$/) {
+        if (/^(\d+)(?:-.*)\.(html|sh|term)$/) {
             print STDERR "reading $_\n" if $verbose;
 
             my $seq = $1;
             my $ext = $2;
             warn "techtalk-pse: $_: command file is not executable (+x)\n"
-                if $ext eq "sh" && ! -x $_;
+                if ($ext eq "sh" || $ext eq "term") && ! -x $_;
 
             my $h = { name => $_, seq => $1, ext => $2, i => $i };
             push @files, $h;
@@ -340,9 +367,31 @@ sub reread_directory
     }
 }
 
+sub window_fullscreen
+{
+    $w->set_decorated (0);
+    $w->fullscreen ();
+    $fullscreen = 1;
+}
+
+sub window_in_corner
+{
+    $w->set_decorated (1);
+    $w->unfullscreen ();
+    $fullscreen = 0;
+
+    my $gwin = $w->get_window();
+    my $monitor = $w->get_display()->get_monitor_at_window($gwin);
+    my $geom = $monitor->get_geometry();
+
+    $width = $geom->{width} / 2;
+    $height = $geom->{height} / 4;
+}
+
 sub run_process
 {
-    $pid = $vte->fork_command("./" . $current->{name}, [], [], undef, 0, 0, 0);
+    my @ret = $vte->spawn_sync('default', ".", ["./" . $current->{name}], [], 'default');
+    $pid = $ret[1];
 }
 
 sub kill_process
@@ -361,6 +410,8 @@ sub switch_slide
 {
     my $action = shift;
 
+    window_fullscreen ();
+
     if ($pid) {
        kill_process ();
     }
@@ -414,6 +465,13 @@ sub update_slide
        }
        # Run a shell command.
        elsif ($current->{ext} eq "sh") {
+            window_in_corner ();
+
+           $notebook->set_current_page ($shpage);
+           run_process ();
+       }
+        # Open a VTE terminal.
+       elsif ($current->{ext} eq "term") {
            $notebook->set_current_page ($vtepage);
            $vte->grab_focus ();
            run_process ();
@@ -447,28 +505,28 @@ sub update_slide
 
 
 sub make_splash_page {
-    my $box = Gtk2::VBox->new();
+    my $box = Gtk3::VBox->new();
 
-    my $title = Gtk2::Label->new ("<b><span size='x-large'>Tech Talk Platinum Supreme Edition (PSE)</span></b>");
+    my $title = Gtk3::Label->new ("<b><span size='x-large'>Tech Talk Platinum Supreme Edition (PSE)</span></b>");
     $title->set_use_markup (1);
 
     $box->pack_start ($title, 0, 1, 0);
 
-    my $vers = Gtk2::Label->new ("<b><span size='large'>@VERSION@</span></b>");
+    my $vers = Gtk3::Label->new ("<b><span size='large'>@VERSION@</span></b>");
     $vers->set_use_markup (1);
     $box->pack_start ($vers, 0, 1, 0);
 
-    my $tagline = Gtk2::Label->new ("<i><span size='large'>Superior technical demonstration software</span></i>");
+    my $tagline = Gtk3::Label->new ("<i><span size='large'>Superior technical demonstration software</span></i>");
     $tagline->set_use_markup (1);
 
     $box->pack_start ($tagline, 0, 1, 0);
-    $box->pack_start (Gtk2::Label->new (""), 0, 1, 0);
-    $box->pack_start (Gtk2::Label->new ("Author: Richard W.M. Jones"), 0, 1, 0);
+    $box->pack_start (Gtk3::Label->new (""), 0, 1, 0);
+    $box->pack_start (Gtk3::Label->new ("Author: Richard W.M. Jones"), 0, 1, 0);
 
-    my $url = Gtk2::Label->new ("<a href='http://people.redhat.com/~rjones'>http;//people.redhat.com/~rjones/</a>");
+    my $url = Gtk3::Label->new ("<a href='http://people.redhat.com/~rjones'>http://people.redhat.com/~rjones/</a>");
     $url->set_use_markup (1);
     $box->pack_start ($url, 0, 1, 0);
-    $box->pack_start (Gtk2::Label->new ("GNU General Public License v2 or above"), 0, 1, 0);
+    $box->pack_start (Gtk3::Label->new ("GNU General Public License v2 or above"), 0, 1, 0);
 
     return $box;
 }
@@ -476,30 +534,30 @@ sub make_splash_page {
 # Make the standard button bar across the top of the page.
 sub make_button_bar
 {
-    my $bbox = Gtk2::Toolbar->new ();
+    my $bbox = Gtk3::Toolbar->new ();
     $bbox->set_style ("GTK_TOOLBAR_TEXT");
 
     my $i = 0;
 
-    my $bquit = Gtk2::ToolButton->new (undef, "Quit");
-    $bquit->signal_connect (clicked => sub { Gtk2->main_quit });
+    my $bquit = Gtk3::ToolButton->new (undef, "Quit");
+    $bquit->signal_connect (clicked => sub { Gtk3::main_quit });
     $bbox->insert ($bquit, $i++);
 
-    my $breload = Gtk2::ToolButton->new (undef, "Reload");
+    my $breload = Gtk3::ToolButton->new (undef, "Reload");
     $breload->signal_connect (clicked => sub { reread_directory () });
     $bbox->insert ($breload, $i++);
 
-    my $bnext = Gtk2::ToolButton->new (undef, "Next slide");
+    my $bnext = Gtk3::ToolButton->new (undef, "Next slide");
     $bnext->signal_connect (clicked => sub { &switch_slide ("NEXT") });
     $bbox->insert ($bnext, $i++);
 
-    my $bback = Gtk2::ToolButton->new (undef, "Back");
+    my $bback = Gtk3::ToolButton->new (undef, "Back");
     $bback->signal_connect (clicked => sub { &switch_slide ("PREV") });
     $bbox->insert ($bback, $i++);
 
-    $bbox->insert (Gtk2::SeparatorToolItem->new (), $i++);
+    $bbox->insert (Gtk3::SeparatorToolItem->new (), $i++);
 
-    my $brestart = Gtk2::ToolButton->new (undef, "Kill & restart");
+    my $brestart = Gtk3::ToolButton->new (undef, "Kill & restart");
     $brestart->signal_connect (clicked =>
                               sub {
                                   kill_process ();
@@ -507,26 +565,26 @@ sub make_button_bar
                               });
     $bbox->insert ($brestart, $i++);
 
-    my $sep = Gtk2::SeparatorToolItem->new ();
+    my $sep = Gtk3::SeparatorToolItem->new ();
     $sep->set_expand (TRUE);
     $sep->set_draw (FALSE);
     $bbox->insert ($sep, $i++);
 
-    my $optsmenu = Gtk2::Menu->new ();
+    my $optsmenu = Gtk3::Menu->new ();
 
-    my $mfirst = Gtk2::MenuItem->new ("First slide");
+    my $mfirst = Gtk3::MenuItem->new ("First slide");
     $mfirst->signal_connect (activate => sub { &switch_slide ("FIRST") });
     $mfirst->show ();
     $optsmenu->append ($mfirst);
 
-    my $mlast = Gtk2::MenuItem->new ("Last slide");
+    my $mlast = Gtk3::MenuItem->new ("Last slide");
     $mlast->signal_connect (activate => sub { &switch_slide ("LAST") });
     $mlast->show ();
     $optsmenu->append ($mlast);
 
-    my $slidesmenu = Gtk2::Menu->new ();
+    my $slidesmenu = Gtk3::Menu->new ();
     foreach (@files) {
-        my $item = Gtk2::MenuItem->new ($_->{name});
+        my $item = Gtk3::MenuItem->new ($_->{name});
         my $index = $_->{i};
         $item->signal_connect (activate => sub { &switch_slide ("I_$index") });
         $item->set_sensitive ($current->{i} != $index);
@@ -534,30 +592,30 @@ sub make_button_bar
         $slidesmenu->append ($item);
     }
 
-    my $mslides = Gtk2::MenuItem->new ("Slides");
+    my $mslides = Gtk3::MenuItem->new ("Slides");
     $mslides->set_submenu ($slidesmenu);
     $mslides->show ();
     $optsmenu->append ($mslides);
 
-    my $sep2 = Gtk2::SeparatorMenuItem->new ();
+    my $sep2 = Gtk3::SeparatorMenuItem->new ();
     $sep2->show ();
     $optsmenu->append ($sep2);
 
-    my $mscreenshot = Gtk2::MenuItem->new ("Take a screenshot");
+    my $mscreenshot = Gtk3::MenuItem->new ("Take a screenshot");
     $mscreenshot->signal_connect (activate => sub { screenshot () });
     $mscreenshot->show ();
     $optsmenu->append ($mscreenshot);
 
-    my $sep3 = Gtk2::SeparatorMenuItem->new ();
+    my $sep3 = Gtk3::SeparatorMenuItem->new ();
     $sep3->show ();
     $optsmenu->append ($sep3);
 
-    my $mquit = Gtk2::MenuItem->new ("Quit");
-    $mquit->signal_connect (activate => sub { Gtk2->main_quit });
+    my $mquit = Gtk3::MenuItem->new ("Quit");
+    $mquit->signal_connect (activate => sub { Gtk3::main_quit });
     $mquit->show ();
     $optsmenu->append ($mquit);
 
-    my $moptions = Gtk2::MenuToolButton->new (undef, "Options");
+    my $moptions = Gtk3::MenuToolButton->new (undef, "Options");
     #$boptions->signal_connect (clicked =>
     #  sub { $optsmenu->popup (undef, undef, undef, undef, ?, ?) } );
     $bbox->insert ($moptions, $i++);
@@ -566,29 +624,11 @@ sub make_button_bar
     return ($bbox, $bquit, $breload, $bnext, $bback, $brestart);
 }
 
-# Try running the external "gnome-screenshot" program, if it's
-# available, else take a screenshot using gdk routines.
+# Try running the external "gnome-screenshot" program
 sub screenshot
 {
     system ("gnome-screenshot");
 
-    if ($? == -1) {
-        # We are going to save the entire screen.
-        my $root = Gtk2::Gdk->get_default_root_window ();
-        my ($width, $height) = $root->get_size;
-
-        # Create blank pixbuf to hold the image.
-        my $gdkpixbuf = Gtk2::Gdk::Pixbuf->new ('rgb',
-                                                0, 8, $width, $height);
-
-        $gdkpixbuf->get_from_drawable ($root, $root->get_colormap (),
-                                       0, 0, 0, 0, $width, $height);
-
-        my $i = 0;
-        $i++ while -f "screenshot$i.png";
-        $gdkpixbuf->save ("screenshot$i.png", 'png');
-    }
-
     return FALSE;
 }
 
@@ -611,8 +651,8 @@ somewhere:
 
 A tech talk consists of HTML files ("slides") and shell scripts.  The
 filenames must start with a number, followed optionally by a
-description, followed by the extension (C<.html> or C<.sh>).  So to
-start our talk with two slides:
+description, followed by the extension (C<.html>, C<.sh> or C<.term>).
+So to start our talk with two slides:
 
  echo "This is the introduction" > 0010-introduction.html
  echo "This is the second slide" > 0020-second.html
@@ -680,9 +720,10 @@ styles as I edit them.  Just start firefox in the talk directory:
 When you edit an HTML file, click the Firefox reload button to
 immediately see your changes.
 
-Tech Talk PSE uses Mozilla embedding to display HTML, which uses the
-same Mozilla engine as Firefox, so what you should see in Firefox
-should be identical to what Tech Talk PSE displays.
+Tech Talk PSE uses WebKit embedding to display HTML.  HTML is
+standardized enough nowadays that what you see in Firefox and other
+browsers should be the same as what Tech Talk PSE displays.
+WebKit-based browsers (Chrome, Safari) should be identical.
 
 =head2 CREATING FIGURES
 
@@ -692,8 +733,8 @@ an C<E<lt>imgE<gt>> tag, eg:
 
  <img src="fig1.gif">
 
-Suitable tools include: XFig, GnuPlot, GraphViz, and many TeX tools
-such as PicTex and in particular TikZ.
+Suitable tools include: Inkscape, XFig, GnuPlot, GraphViz, and many
+TeX tools such as PicTex and in particular TikZ.
 
 =head2 EMBEDDING VIDEOS, ANIMATIONS, ETC.
 
@@ -722,9 +763,14 @@ either rename or make a symbolic link to the slide name:
 
 =head2 TIPS FOR WRITING SHELL SCRIPTS
 
-Make sure each C<*.sh> file you write is executable, otherwise Tech
-Talk PSE won't be able to run it.  (The program gives a warning if you
-forget this).
+Make sure each C<*.sh> or C<*.term> file you write is executable,
+otherwise Tech Talk PSE won't be able to run it.  (The program gives a
+warning if you forget this).
+
+The difference between C<*.sh> (shell script) and C<*.term> (a
+terminal script) is that a shell script runs any commands, usually
+graphical commands, whereas a terminal script runs in a full screen
+terminal.
 
 A good idea is to start each script by sourcing some common functions.
 All my scripts start with:
@@ -761,30 +807,21 @@ In C<functions>, I have:
      # when it exits.
      chmod -w $HISTFILE
  
-     # Run gnome-terminal.
-     exec \
-         gnome-terminal \
-         --window \
-         --geometry=+100+100 \
-         --hide-menubar \
-         --disable-factory \
-         -e '/bin/bash --norc' \
-         "$@"
+     # Execute a shell.
+     bash --norc "$@"
  }
 
 By initializing the shell history, during your talk you can rapidly
 recall commands to start parts of the demonstration just by hitting
-the Up arrow.  A complete shell script from one of my talks would look
-like this:
+the Up arrow.  A complete terminal script from one of my talks would
+look like this:
 
  #!/bin/bash -
  source functions
  add_history guestfish -i debian.img
- terminal --title="Examining a Debian guest image in guestfish"
+ terminal
 
-This is just a starting point for your own scripts.  You may want to
-use a different terminal, such as xterm, and you may want to adjust
-terminal fonts.
+This is just a starting point for your own scripts.
 
 =head1 REFERENCE
 
@@ -794,7 +831,7 @@ Tech Talk PSE displays the slides in the directory in lexicographic
 order (the same order as C<LANG=C ls -1>).  Only files matching the
 following regexp are considered:
 
- ^(\d+)(?:-.*)\.(html|sh)$
+ ^(\d+)(?:-.*)\.(html|sh|term)$
 
 For future compatibility, you should ensure that every slide has a
 unique numeric part (ie. I<don't> have C<0010-aaa.html> and
@@ -862,7 +899,7 @@ that it took to get them all in one place.
 
 I don't think you can get away with spending less than two full days
 preparing a talk, if you want to master the topic and draw up accurate
-slides.  Steve Jobs is reputed to spend weeks preparing his annual
+slides.  Steve Jobs was reputed to spend weeks preparing his annual
 sales talk to the Apple faithful.
 
 B<Thirdly:> Now that you're going to write your talk as an essay, what
@@ -893,9 +930,11 @@ The Cognitive Style of PowerPoint, Tufte, Edward R.
 
 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
 
+Daniel BerrangĂ© L<http://berrange.com/>
+
 =head1 COPYRIGHT
 
-Copyright (C) 2010 Red Hat Inc.
+Copyright (C) 2010-2012 Red Hat Inc.
 
 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