X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=techtalk-pse.pl;h=843f6c774064c87d9cdfccb825fd840561c30d41;hb=9861482b4e7f7b32cead8b76cb555bd59a89670a;hp=8a3abb6fd1a97c229bafbcaf74875a31ba0e9385;hpb=ed0ab4e14ee8647eb63cbfe2ab3c9a74641949cb;p=techtalk-pse.git diff --git a/techtalk-pse.pl b/techtalk-pse.pl index 8a3abb6..843f6c7 100755 --- a/techtalk-pse.pl +++ b/techtalk-pse.pl @@ -23,6 +23,7 @@ use warnings; use strict; use utf8; +use POSIX qw(setsid); use Pod::Usage; use Getopt::Long; use Cwd qw(getcwd abs_path); @@ -165,49 +166,7 @@ if ($version) { die "techtalk-pse: cannot use --start and --last options together\n" if defined $last && defined $start; -# --mozembed runs Gtk2::MozEmbed as a subprocess, because MozEmbed -# is very crashy. -if ($mozembed) { - my $r = 0; - - my $w = Gtk2::Window->new (); - my $vbox = Gtk2::VBox->new (); - my $moz = Gtk2::MozEmbed->new (); - my $bbox = Gtk2::HButtonBox->new (); - - $vbox->pack_start ($bbox, 0, 0, 0); - $vbox->add ($moz); - $w->fullscreen (); - #$w->set_default_size (640, 480); - $w->add ($vbox); - - $bbox->set_layout ('start'); - my $bnext = Gtk2::Button->new ("Next slide"); - $bnext->signal_connect (clicked => sub { $r = 0; Gtk2->main_quit }); - $bbox->add ($bnext); - - my $bback = Gtk2::Button->new ("Back"); - $bback->signal_connect (clicked => sub { $r = 1; Gtk2->main_quit }); - $bbox->add ($bback); - - my $bquit = Gtk2::Button->new ("Quit"); - $bquit->signal_connect (clicked => sub { $r = 2; Gtk2->main_quit }); - $bbox->add ($bquit); - $bbox->set_child_secondary ($bquit, 1); - - $w->signal_connect (delete_event => sub { - Gtk2->main_quit; - return FALSE; - }); - $w->show_all (); - - $moz->load_url ($ARGV[0]); - Gtk2->main; - - exit $r; -} - -die "techtalk-pse: too many arguments\n" if @ARGV >= 2; +die "techtalk-pse: too many arguments\n" if !$mozembed && @ARGV >= 2; # Get the true name of the program. $0 = abs_path ($0); @@ -224,35 +183,41 @@ if (@ARGV > 0) { } } +# Get the talk directory and set environment variable $talkdir +# which is inherited by all the scripts. +my $talkdir = getcwd; +$ENV{talkdir} = $talkdir; + # Get the files. my @files; my %files; -my %groups; sub reread_directory { @files = (); - %groups = (); + my $i = 0; foreach (glob ("*")) { - if (/^(\d+)([A-Z])?(?:-.*)\.(html|sh)$/) { + if (/^(\d+)(?:-.*)\.(html|sh)$/) { print STDERR "reading $_\n" if $verbose; my $seq = $1; - my $pos = $2 || "A"; - my $ext = $3; + my $ext = $2; warn "techtalk-pse: $_: command file is not executable (+x)\n" if $ext eq "sh" && ! -x $_; - my $h = { name => $_, seq => $1, pos => $2, ext => $3 }; + my $h = { name => $_, seq => $1, ext => $2, i => $i }; push @files, $h; $files{$_} = $h; - - $groups{$seq} = [] unless exists $groups{$seq}; - push @{$groups{$seq}}, $h; + $i++; } else { print STDERR "ignoring $_\n" if $verbose; } } + + if (@files > 0) { + $files[0]->{first} = 1; + $files[$#files]->{last} = 1; + } } reread_directory (); print STDERR "read ", 0+@files, " files\n" if $verbose; @@ -260,6 +225,11 @@ if (@files == 0) { warn "techtalk-pse: no files found, continuing anyway ...\n" } +# Run with --mozembed: see below. +run_mozembed () if $mozembed; + +# Else, normal run of the program ... + # Work out what slide we're starting on. my $current; if (defined $current) { @@ -274,18 +244,19 @@ if ($splash) { my $w = Gtk2::AboutDialog->new; $w->set_authors ("Richard W.M. Jones"); $w->set_comments ( - "Superior technical demonstration software\n". - "\n". - "Keys\n". - "↑ — Go back one slide\n". - "↓ — Go forward one slide\n" + "Superior technical demonstration software\n" ); $w->set_program_name ("Tech Talk Platinum Supreme Edition (PSE)"); $w->set_version ("@VERSION@"); $w->set_website ("http://people.redhat.com/~rjones"); $w->set_license ("GNU General Public License v2 or above"); - $w->signal_connect (destroy => sub { Gtk2->main_quit }); - $w->show_all; + $w->run; + print STDERR "calling \$w->destroy on about dialog\n" if $verbose; + $w->destroy; + + # The dialog doesn't really get destroyed here. We have + # to add this hack to really destroy it. + Glib::Idle->add (sub { Gtk2->main_quit; return FALSE; }); Gtk2->main; } @@ -296,17 +267,13 @@ MAIN: while (1) { print STDERR "go = $go\n" if $verbose; last MAIN if $go eq "QUIT"; - my $i = 0; - FOUND: { - foreach (@files) { - last FOUND if $files[$i]->{name} eq $current->{name}; - $i++; - } - die "internal error: cannot find \$current in \@files" - } - print STDERR "found current entry at i = $i\n" if $verbose; + 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 { @@ -314,6 +281,7 @@ MAIN: while (1) { $_ = ; } + # Reread directory between slides. reread_directory (); if (defined $current && !exists $files{$current->{name}}) { @@ -323,45 +291,539 @@ MAIN: while (1) { } } -sub show_slide { +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. - my $cwd = getcwd; - my $url = "file://" . $cwd . "/" . $slide->{name}; - my @cmd = ($0, "--mozembed", $url); - system (@cmd); - die "failed to execute subcommand: $!\n" if $? == -1; - if ($? & 127) { - # Subcommand probably segfaulted, just continue to next slide. - return "NEXT"; - } else { - my $r = $? >> 8; - if ($r == 0) { - return "NEXT"; - } elsif ($r == 1) { - return "PREV"; - } elsif ($r == 2) { - return "QUIT"; + # 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 () { + 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") { - system ("PATH=.:\$PATH " . $slide->{name}); - return "NEXT"; + 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); + + my $bscreenshot = Gtk2::MenuItem->new ("Take a screenshot"); + $bscreenshot->signal_connect (activate => sub { screenshot () }); + $bscreenshot->show (); + $optsmenu->append ($bscreenshot); + + my $sep3 = Gtk2::SeparatorMenuItem->new (); + $sep3->show (); + $optsmenu->append ($sep3); + + my $bquit = Gtk2::MenuItem->new ("Quit"); + $bquit->signal_connect (activate => sub { \&$cb ("QUIT") }); + $bquit->show (); + $optsmenu->append ($bquit); + + my $boptions = Gtk2::MenuToolButton->new (undef, "Options"); + #$boptions->signal_connect (clicked => + # sub { $optsmenu->popup (undef, undef, undef, undef, ?, ?) } ); + $bbox->insert ($boptions, $i++); + $boptions->set_menu ($optsmenu); + + return $bbox; +} + +# Try running the external "gnome-screenshot" program, if it's +# available, else take a screenshot using gdk routines. +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; +} + 1; +__END__ + =head1 TUTORIAL +=head2 START WRITING A TALK + +[Before you start writing your real talk, I urge you to read +L below]. + +To start your talk, all you have to do is to make a new directory +somewhere: + + mkdir talk + cd talk + +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: + + echo "This is the introduction" > 0010-introduction.html + echo "This is the second slide" > 0020-second.html + +To run it, run the command from within the talk directory: + + techtalk-pse + +Any other file in the directory is ignored, so if you want to add +Makefiles, version control files etc, just go ahead. + +=head2 TIPS FOR WRITING HTML + +You may have your own techniques and tools for writing HTML, so +this section is just to share my ideas. I start every +HTML file with a standard stylesheet and Javascript header: + + + + +That just ensures that I can put common styling instructions for all +my slides in a single file (C), and I have one place where +I can add all Javascript, if I need to use any (C). + +=head3 BACKGROUNDS, FONTS AND LOGOS + +To add a common background and font size to all slides, put this in +C: + + body { + font-size: 24pt; + background: url(background-image.jpg) no-repeat; + } + +To add a logo in one corner: + + body { + background: url(logo.jpg) top right no-repeat; + } + +=head3 SCALING AND CENTERING + +Scaling slide text and images so that they appear at the same +proportionate size for any screen resolution can be done using +Javascript. (See +L). + +If you want to center text horizontally, use CSS, eg: + + p.center { + text-align: center; + } + +To center text vertically, CSS3 is supposed to offer a solution some +time, but while you're waiting for that try +L. + +=head3 PREVIEWING HTML + +I find it helpful to have Firefox open to display the HTML files and +styles as I edit them. Just start firefox in the talk directory: + + firefox file://$(pwd) & + +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. + +=head2 CREATING FIGURES + +Use your favorite tool to draw the figure, convert it to an image (in +any format that the Mozilla engine can display) and include it using +an CimgE> tag, eg: + + + +Suitable tools include: XFig, GnuPlot, GraphViz, and many TeX tools +such as PicTex and in particular TikZ. + +=head2 EMBEDDING VIDEOS, ANIMATIONS, ETC. + +Using HTML 5, embedding videos in the browser is easy. See: +L + +For animations, you could try L which has a +Javascript back-end. There are many other possibilities. + +If you are B that the venue will have an internet connection, +why not embed a YouTube video. + +=head2 DISPLAYING EXISTING WEB PAGES + +Obviously you could just have an HTML file that contains a redirect to +the public web page: + + + +However if you want your talk to work offline, then it's better to +download the web page in advance, eg. using Firefox's "Save Page As +-E Web Page, complete" feature, into the talk directory, then +either rename or make a symbolic link to the slide name: + + ln -s "haXe - Welcome to haXe.html" 0010-haxe-homepage.html + +=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). + +A good idea is to start each script by sourcing some common functions. +All my scripts start with: + + #!/bin/bash - + source functions + +where C is another file (ignored by Tech Talk PSE) which +contains common functions for setting shell history and starting a +terminal. + +In C, I have: + + # -*- shell-script -*- + + # Place any local environment variables required in 'local'. + if [ -f local ]; then source local; fi + + export PS1="$ " + + export HISTFILE=$talkdir/history + + rm -f $HISTFILE + touch $HISTFILE + + add_history () + { + echo "$@" >> $HISTFILE + } + + terminal () + { + # Make $HISTFILE unwritable so the shell won't update it + # when it exits. + chmod -w $HISTFILE + + # Run gnome-terminal. + exec \ + gnome-terminal \ + --window \ + --geometry=+100+100 \ + --hide-menubar \ + --disable-factory \ + -e '/bin/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: + + #!/bin/bash - + source functions + add_history guestfish -i debian.img + terminal --title="Examining a Debian guest image in guestfish" + +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. + =head1 REFERENCE +=head2 ORDER OF FILES + +Tech Talk PSE displays the slides in the directory in lexicographic +order (the same order as C). Only files matching the +following regexp are considered: + + ^(\d+)(?:-.*)\.(html|sh)$ + +For future compatibility, you should ensure that every slide has a +unique numeric part (ie. I have C<0010-aaa.html> and +C<0010-bbb.html>). This is because in future we want to have the +ability to display multiple files side by side. + +Also for future compatibility, I use file names that have an +uppercase letter immediately after the numeric part. This is because +in future we want to allow placement hints using filenames like +C<0010L-on-the-left.html> and C<0010R-on-the-right.html>. + +=head2 BASE URL AND CURRENT DIRECTORY + +The base URL is set to the be the directory containing the talk files. +Thus you should use relative paths, eg: + + + +You can also place assets into subdirectories, because subdirectories +are ignored by Tech Talk PSE, eg: + + + +When running shell scripts, the current directory is also set to be +the directory containing the talk files, so the same rules about using +relative paths apply there too. + +The environment variable C<$talkdir> is exported to scripts and it +contains the absolute path of the directory containing the talk files. +When a script is run, the current directory is the same as +C<$talkdir>, but if your script changes directory (eg. into a +subdirectory containing supporting files) then it can be useful to use +C<$talkdir> to refer back to the original directory. + =head1 WHAT MAKES A GOOD TALK +I like what Edward Tufte writes, for example his evisceration of +PowerPoint use at NASA here: +L + +However it is sometimes hard to translate his ideas into clear +presentations, and not all of that is the fault of the tools. Here +are my thoughts and rules on how to deliver a good talk. + +B Before you start drawing any slides at +all, write your talk as a short essay. + +This is the number one mistake that presenters make, and it is partly +a tool fault, because PowerPoint, OpenOffice, even Tech Talk PSE, all +open up on an initial blank slide, inviting you to write a title and +some bullet points. If you start that way, you will end up using the +program as a kind of clumsy outlining tool, and then reading that +outline to your audience. That's boring and a waste of time for you +and your audience. (It would be quicker for them just to download the +talk and read it at home). + +B How long do you want to spend preparing the talk? A good +talk, with a sound essay behind it, well thought out diagrams and +figures, and interesting demonstrations, takes many hours to prepare. +How many hours? I would suggest thinking about how many hours of +effort your audience are putting in. Even just 20 people sitting +there for half an hour is 10 man-hours of attention, and that is a +very small talk, and doesn't include all the extra time and hassle +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 +sales talk to the Apple faithful. + +B Now that you're going to write your talk as an essay, what +should go in the slides? I would say that you should consider +delivering the essay, I the slides, to people who don't make the +talk. An essay can be turned into an article or blog posting, whereas +even "read-out-the-bullet-point" slides have a low information +density, large size, and end-user compatibility problems (*.pptx +anyone?). + +What, then, goes on the slides? Anything you cannot just say: +diagrams, graphs, videos, animations, and of course (only with Tech +Talk PSE!) demonstrations. + +B Once you've got your talk as an essay and slides, practice, +practice and practice again. Deliver the talk to yourself in the +mirror, to your colleagues. Practice going backwards and forwards +through the slides, using your actual laptop and the software so you +know what to click and what keys to press. Partly memorize what you +are going to say (but use short notes written on paper if you need +to). + =head1 SEE ALSO The Cognitive Style of PowerPoint, Tufte, Edward R.