Add environment variable.
[techtalk-pse.git] / techtalk-pse.pl
1 #!/usr/bin/perl -w
2 # -*- perl -*-
3 # @configure_input@
4 #
5 # Tech Talk PSE
6 # Copyright (C) 2010 Red Hat Inc.
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 use warnings;
23 use strict;
24 use utf8;
25
26 use POSIX qw(setsid);
27 use Pod::Usage;
28 use Getopt::Long;
29 use Cwd qw(getcwd abs_path);
30 use Glib qw(TRUE FALSE);
31 use Gtk2 -init;
32 use Gtk2::MozEmbed;
33
34 =encoding utf8
35
36 =head1 NAME
37
38 techtalk-pse - superior technical demonstration software
39
40 =head1 SYNOPSIS
41
42  cd /path/to/talk/; techtalk-pse
43
44  techtalk-pse /path/to/talk/
45
46 =head1 DESCRIPTION
47
48 Tech Talk "Platinum Supreme Edition" (PSE) is Linux Presentation
49 Software designed by technical people to give technical software
50 demonstrations to other technical people.  It is designed to be simple
51 to use (for people who know how to use an editor and the command line)
52 and powerful, so that you can create informative, technically accurate
53 and entertaining talks and demonstrations.
54
55 Tech Talk PSE is good at opening editors at the right place, opening
56 shell prompts with preloaded history, compiling and running things
57 during the demonstration, displaying text, photos, figures and video.
58
59 Tech Talk PSE is I<bad> at slide effects, chart junk and bullet
60 points.
61
62 This manual page covers all the documentation you will need to use
63 Tech Talk PSE.  The next section covers running the tool from the
64 command line.  After that there is a L</TUTORIAL> section to get you
65 started.  Then there is a detailed L</REFERENCE> section.  Finally
66 there is a discussion on L<WHAT MAKES A GOOD TALK>.
67
68 =head1 RUNNING THE TOOL FROM THE COMMAND LINE
69
70 A Tech Talk PSE talk is not a single file, but a directory full of
71 files.  (If you want to start a new talk, see the L</TUTORIAL> section
72 below).  To display or run the talk, change into the directory
73 containing all those files and run the C<techtalk-pse> command:
74
75  cd /path/to/talk/; techtalk-pse
76
77 You can also run C<techtalk-pse> without changing directory, instead
78 specifying the path to the talk:
79
80  techtalk-pse /path/to/talk/
81
82 =head2 OPTIONS
83
84 =over 4
85
86 =cut
87
88 my $help;
89
90 =item B<--help>
91
92 Display brief help and exit.
93
94 =cut
95
96 my $last;
97
98 =item B<--last>
99
100 Start at the last slide.
101
102 You cannot use this with the B<-n> / B<--start> option.
103
104 =cut
105
106 my $start;
107
108 =item B<-n SLIDE> | B<--start SLIDE>
109
110 Start at the named slide.  I<SLIDE> is the shortest unique prefix of
111 the slide name, so to start at a slide named
112 I<00010-introduction.html>, you could use I<-n 00010> or I<-n 00010-intro>,
113 or give the full filename I<-n 00010-introduction.html>.
114
115 The default is to start at the first slide in the talk.
116
117 =cut
118
119 my $splash = 1;
120
121 =item B<--no-splash>
122
123 Don't display the initial "splash" screen which advertises Tech Talk
124 PSE to your audience.  Just go straight into the talk.
125
126 =cut
127
128 my $verbose;
129
130 =item B<--verbose>
131
132 Display verbose messages, useful for debugging or tracing
133 what the program is doing.
134
135 =cut
136
137 my $version;
138
139 =item B<--version>
140
141 Display version number and exit.
142
143 =cut
144
145 my $mozembed;
146 my $mozembed_first;
147 my $mozembed_last;
148
149 GetOptions ("help|?" => \$help,
150             "last" => \$last,
151             "mozembed" => \$mozembed,
152             "mozembed-first" => \$mozembed_first,
153             "mozembed-last" => \$mozembed_last,
154             "n=s" => \$start,
155             "splash!" => \$splash,
156             "start=s" => \$start,
157             "verbose" => \$verbose,
158             "version" => \$version,
159     ) or pod2usage (2);
160
161 =back
162
163 =cut
164
165 pod2usage (1) if $help;
166 if ($version) {
167     print "@PACKAGE@ @VERSION@\n";
168     exit
169 }
170 die "techtalk-pse: cannot use --start and --last options together\n"
171     if defined $last && defined $start;
172
173 # Run with --mozembed: see below.
174 run_mozembed () if $mozembed;
175
176 # Normal run of the program.
177 die "techtalk-pse: too many arguments\n" if @ARGV >= 2;
178
179 # Get the true name of the program.
180 $0 = abs_path ($0);
181
182 # Locate the talk.
183 if (@ARGV > 0) {
184     my $d = $ARGV[0];
185     if (-d $d) {
186         chdir $d or die "techtalk-pse: chdir: $d: $!";
187     } else {
188         # XXX In future allow people to specify an archive and unpack
189         # it for them.
190         die "techtalk-pse: argument is not a directory"
191     }
192 }
193
194 # Get the talk directory and set environment variable $talkdir
195 # which is inherited by all the scripts.
196 my $talkdir = getcwd;
197 $ENV{talkdir} = $talkdir;
198
199 # Get the files.
200 my @files;
201 my %files;
202 sub reread_directory
203 {
204     @files = ();
205
206     my $i = 0;
207     foreach (glob ("*")) {
208         if (/^(\d+)(?:-.*)\.(html|sh)$/) {
209             print STDERR "reading $_\n" if $verbose;
210
211             my $seq = $1;
212             my $ext = $2;
213             warn "techtalk-pse: $_: command file is not executable (+x)\n"
214                 if $ext eq "sh" && ! -x $_;
215
216             my $h = { name => $_, seq => $1, ext => $2, i => $i };
217             push @files, $h;
218             $files{$_} = $h;
219             $i++;
220         } else {
221             print STDERR "ignoring $_\n" if $verbose;
222         }
223     }
224
225     if (@files > 0) {
226         $files[0]->{first} = 1;
227         $files[$#files]->{last} = 1;
228     }
229 }
230 reread_directory ();
231 print STDERR "read ", 0+@files, " files\n" if $verbose;
232 if (@files == 0) {
233     warn "techtalk-pse: no files found, continuing anyway ...\n"
234 }
235
236 # Work out what slide we're starting on.
237 my $current;
238 if (defined $current) {
239     die "start slide not implemented yet XXX"
240 }
241 elsif (@files) {
242     $current = $files[0];
243 }
244 # else $current is undefined
245
246 if ($splash) {
247     my $w = Gtk2::AboutDialog->new;
248     $w->set_authors ("Richard W.M. Jones");
249     $w->set_comments (
250         "Superior technical demonstration software\n"
251         );
252     $w->set_program_name ("Tech Talk Platinum Supreme Edition (PSE)");
253     $w->set_version ("@VERSION@");
254     $w->set_website ("http://people.redhat.com/~rjones");
255     $w->set_license ("GNU General Public License v2 or above");
256     $w->run;
257     print STDERR "calling \$w->destroy on about dialog\n" if $verbose;
258     $w->destroy;
259 }
260
261 MAIN: while (1) {
262     if (defined $current) {
263         my $go = show_slide ($current);
264         if (defined $go) {
265             print STDERR "go = $go\n" if $verbose;
266             last MAIN if $go eq "QUIT";
267
268             my $i = $current->{i};
269             print STDERR "i = $i\n" if $verbose;
270             $i-- if $go eq "PREV" && $i > 0;
271             $i++ if $go eq "NEXT" && $i+1 < @files;
272             $current = $files[$i];
273         }
274     } else {
275         print "No slides found.  Press any key to reload directory ...\n";
276         $_ = <STDIN>;
277     }
278
279     # Reread directory between slides.
280     reread_directory ();
281
282     if (defined $current && !exists $files{$current->{name}}) {
283         # Current slide was deleted.
284         undef $current;
285         $current = $files[0] if @files;
286     }
287 }
288
289 sub show_slide
290 {
291     my $slide = shift;
292
293     # Display an HTML page.
294     if ($slide->{ext} eq "html") {
295         # MozEmbed is incredibly crashy, so we run ourself as a
296         # subprocess, so when it segfaults we don't care.
297         my @cmd = ($0, "--mozembed");
298         push @cmd, "--mozembed-first" if exists $slide->{first};
299         push @cmd, "--mozembed-last" if exists $slide->{last};
300         my $url = "file://$talkdir/" . $slide->{name};
301         push @cmd, $url;
302         print STDERR "running subcommand: ", join (" ", @cmd), "\n"
303             if $verbose;
304         system (@cmd);
305         die "failed to execute subcommand: ", join(" ", @cmd), ": $!\n"
306             if $? == -1;
307         if ($? & 127) {
308             # Subcommand probably segfaulted, just continue to next slide.
309             return "NEXT";
310         } else {
311             my $r = $? >> 8;
312             if ($r == 0) {
313                 return "NEXT";
314             } elsif ($r == 1) {
315                 return "PREV";
316             } elsif ($r == 2) {
317                 return "QUIT";
318             }
319         }
320     }
321     # Run a shell command.
322     elsif ($slide->{ext} eq "sh") {
323         my $pid;
324         # http://docstore.mik.ua/orelly/perl/cookbook/ch10_17.htm
325         local *run_process = sub {
326             $pid = fork ();
327             die "fork: $!" unless defined $pid;
328             unless ($pid) {
329                 # Child.
330                 POSIX::setsid ();
331                 exec ("./".$slide->{name});
332                 die "failed to execute command: ", $slide->{name}, ": $!";
333             }
334             # Parent returns.
335         };
336         local *kill_process = sub {
337             print STDERR "sending TERM signal to process group $pid\n"
338                 if $verbose;
339             kill "TERM", -$pid;
340         };
341         run_process ();
342
343         my $r = "NEXT";
344
345         my $w = Gtk2::Window->new ();
346
347         my $s = $w->get_screen;
348         $w->set_default_size ($s->get_width, -1);
349         $w->move (0, 0);
350         $w->set_decorated (0);
351
352         my $bbox = Gtk2::HButtonBox->new ();
353         $bbox->set_layout ('start');
354
355         my $bnext = Gtk2::Button->new ("Next slide");
356         $bnext->signal_connect (clicked => sub { $r = "NEXT"; $w->destroy });
357         $bnext->set_sensitive (!(exists $slide->{last}));
358         $bbox->add ($bnext);
359
360         my $bback = Gtk2::Button->new ("Back");
361         $bback->signal_connect (clicked => sub { $r = "PREV"; $w->destroy });
362         $bback->set_sensitive (!(exists $slide->{first}));
363         $bbox->add ($bback);
364
365         my $brestart = Gtk2::Button->new ("Kill & restart");
366         $brestart->signal_connect (clicked => sub {
367             kill_process ();
368             run_process ();
369         });
370         $bbox->add ($brestart);
371
372         my $bquit = Gtk2::Button->new ("Quit");
373         $bquit->signal_connect (clicked => sub { $r = "QUIT"; $w->destroy });
374         $bbox->add ($bquit);
375         $bbox->set_child_secondary ($bquit, 1);
376
377         $w->add ($bbox);
378
379         $w->signal_connect (destroy => sub {
380             Gtk2->main_quit;
381             return FALSE;
382         });
383         $w->show_all ();
384
385         Gtk2->main;
386
387         kill_process ();
388         print STDERR "returning r=$r\n" if $verbose;
389         return $r;
390     }
391 }
392
393 # If invoked with the --mozembed parameter then we just display a
394 # single page.  This is just to prevent crashes in MozEmbed from
395 # killing the whole program.
396 sub run_mozembed
397 {
398     my $r = 0;
399
400     my $w = Gtk2::Window->new ();
401     my $vbox = Gtk2::VBox->new ();
402     my $moz = Gtk2::MozEmbed->new ();
403
404     my $bbox = Gtk2::HButtonBox->new ();
405     $bbox->set_layout ('start');
406
407     $vbox->pack_start ($bbox, 0, 0, 0);
408     $vbox->add ($moz);
409     $w->fullscreen ();
410     #$w->set_default_size (640, 480);
411     $w->add ($vbox);
412
413     my $bnext = Gtk2::Button->new ("Next slide");
414     $bnext->signal_connect (clicked => sub { $r = 0; $w->destroy });
415     $bnext->set_sensitive (!$mozembed_last);
416     $bbox->add ($bnext);
417
418     my $bback = Gtk2::Button->new ("Back");
419     $bback->signal_connect (clicked => sub { $r = 1; $w->destroy });
420     $bback->set_sensitive (!$mozembed_first);
421     $bbox->add ($bback);
422
423     my $bquit = Gtk2::Button->new ("Quit");
424     $bquit->signal_connect (clicked => sub { $r = 2; $w->destroy });
425     $bbox->add ($bquit);
426     $bbox->set_child_secondary ($bquit, 1);
427
428     $w->signal_connect (destroy => sub {
429         Gtk2->main_quit;
430         return FALSE;
431     });
432     $w->show_all ();
433
434     $moz->load_url ($ARGV[0]);
435     Gtk2->main;
436
437     exit $r;
438 }
439
440 1;
441
442 =head1 TUTORIAL
443
444 =head2 START WRITING A TALK
445
446 [Before you start writing your real talk, I urge you to read
447 L</WHAT MAKES A GOOD TALK> below].
448
449 To start your talk, all you have to do is to make a new directory
450 somewhere:
451
452  mkdir talk
453  cd talk
454
455 A tech talk consists of HTML files ("slides") and shell scripts.  The
456 filenames must start with a number, followed optionally by a
457 description, followed by the extension (C<.html> or C<.sh>).  So to
458 start our talk with two slides:
459
460  echo "This is the introduction" > 0010-introduction.html
461  echo "This is the second slide" > 0020-second.html
462
463 To run it, run the command from within the talk directory:
464
465  techtalk-pse
466
467 Any other file in the directory is ignored, so if you want to add
468 Makefiles, version control files etc, just go ahead.
469
470 =head2 TIPS FOR WRITING HTML
471
472 You may have your own techniques and tools for writing HTML, so
473 this section is just to share my ideas.  I start every
474 HTML file with a standard stylesheet and Javascript header:
475
476  <link rel="stylesheet" href="style.css" type="text/css"/>
477  <script src="code.js" type="text/javascript"></script>
478
479 That just ensures that I can put common styling instructions for all
480 my slides in a single file (C<style.css>), and I have one place where
481 I can add all Javascript, if I need to use any (C<code.js>).
482
483 =head3 BACKGROUNDS, FONTS AND LOGOS
484
485 To add a common background and font size to all slides, put this in
486 C<style.css>:
487
488  body {
489      font-size: 24pt;
490      background: url(background-image.jpg) no-repeat;
491  }
492
493 To add a logo in one corner:
494
495  body {
496      background: url(logo.jpg) top right no-repeat;
497  }
498
499 =head3 SCALING AND CENTERING
500
501 Scaling slide text and images so that they appear at the same
502 proportionate size for any screen resolution can be done using
503 Javascript.  (See
504 L<https://developer.mozilla.org/En/DOM/window.innerHeight>).
505
506 If you want to center text horizontally, use CSS, eg:
507
508  p.center {
509      text-align: center;
510  }
511
512 To center text vertically, CSS3 is supposed to offer a solution some
513 time, but while you're waiting for that try
514 L<http://www.w3.org/Style/Examples/007/center#vertical>.
515
516 =head3 PREVIEWING HTML
517
518 I find it helpful to have Firefox open to display the HTML files and
519 styles as I edit them.  Just start firefox in the talk directory:
520
521  firefox file://$(pwd) &
522
523 When you edit an HTML file, click the Firefox reload button to
524 immediately see your changes.
525
526 Tech Talk PSE uses Mozilla embedding to display HTML, which uses the
527 same Mozilla engine as Firefox, so what you should see in Firefox
528 should be identical to what Tech Talk PSE displays.
529
530 =head2 CREATING FIGURES
531
532 Use your favorite tool to draw the figure, convert it to an image (in
533 any format that the Mozilla engine can display) and include it using
534 an C<E<lt>imgE<gt>> tag, eg:
535
536  <img src="fig1.gif">
537
538 Suitable tools include: XFig, GnuPlot, GraphViz, and many TeX tools
539 such as PicTex and in particular TikZ.
540
541 =head2 EMBEDDING VIDEOS, ANIMATIONS, ETC.
542
543 Using HTML 5, embedding videos in the browser is easy.  See:
544 L<https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox>
545
546 For animations, you could try L<Haxe|http://haxe.org/> which has a
547 Javascript back-end.  There are many other possibilities.
548
549 If you are B<sure> that the venue will have an internet connection,
550 why not embed a YouTube video.
551
552 =head2 DISPLAYING EXISTING WEB PAGES
553
554 Obviously you could just have an HTML file that contains a redirect to
555 the public web page:
556
557  <meta http-equiv="Refresh" content="0; url=http://www.example.com/">
558
559 However if you want your talk to work offline, then it's better to
560 download the web page in advance, eg. using Firefox's "Save Page As
561 -E<gt> Web Page, complete" feature, into the talk directory, then
562 either rename or make a symbolic link to the slide name:
563
564  ln -s "haXe - Welcome to haXe.html" 0010-haxe-homepage.html
565
566 =head2 TIPS FOR WRITING SHELL SCRIPTS
567
568 Make sure each C<*.sh> file you write is executable, otherwise Tech
569 Talk PSE won't be able to run it.  (The program gives a warning if you
570 forget this).
571
572 A good idea is to start each script by sourcing some common functions.
573 All my scripts start with:
574
575  #!/bin/bash -
576  source functions
577
578 where C<functions> is another file (ignored by Tech Talk PSE) which
579 contains common functions for setting shell history and starting a
580 terminal.
581
582 In C<functions>, I have:
583
584  # -*- shell-script -*-
585  export PS1="$ "
586  export HISTFILE=/tmp/history
587  rm -f $HISTFILE
588  
589  add_history ()
590  {
591      echo "$@" >> $HISTFILE
592  }
593  
594  terminal ()
595  {
596      exec \
597          gnome-terminal \
598          --window \
599          --geometry=+100+100 \
600          --hide-menubar \
601          --disable-factory \
602          -e '/bin/bash --norc' \
603          "$@"
604  }
605
606 By initializing the shell history, during your talk you can rapidly
607 recall commands to start parts of the demonstration just by hitting
608 the Up arrow.  A complete shell script from one of my talks would look
609 like this:
610
611  #!/bin/bash -
612  source functions
613  add_history guestfish -i debian.img
614  terminal --title="Examining a Debian guest image in guestfish"
615
616 This is just a starting point for your own scripts.  You may want to
617 use a different terminal, such as xterm, and you may want to adjust
618 terminal fonts.
619
620 =head1 REFERENCE
621
622 =head2 ORDER OF FILES
623
624 Tech Talk PSE displays the slides in the directory in lexicographic
625 order (the same order as C<LANG=C ls -1>).  Only files matching the
626 following regexp are considered:
627
628  ^(\d+)(?:-.*)\.(html|sh)$
629
630 For future compatibility, you should ensure that every slide has a
631 unique numeric part (ie. I<don't> have C<0010-aaa.html> and
632 C<0010-bbb.html>).  This is because in future we want to have the
633 ability to display multiple files side by side.
634
635 Also for future compatibility, I<don't> use file names that have an
636 uppercase letter immediately after the numeric part.  This is because
637 in future we want to allow placement hints using filenames like
638 C<0010L-on-the-left.html> and C<0010R-on-the-right.html>.
639
640 =head2 BASE URL AND CURRENT DIRECTORY
641
642 The base URL is set to the be the directory containing the talk files.
643 Thus you should use relative paths, eg:
644
645  <img src="fig1.gif">
646
647 You can also place assets into subdirectories, because subdirectories
648 are ignored by Tech Talk PSE, eg:
649
650  <img src="images/fig1.gif">
651
652 When running shell scripts, the current directory is also set to be
653 the directory containing the talk files, so the same rules about using
654 relative paths apply there too.
655
656 The environment variable C<$talkdir> is exported to scripts and it
657 contains the absolute path of the directory containing the talk files.
658 When a script is run, the current directory is the same as
659 C<$talkdir>, but if your script changes directory (eg. into a
660 subdirectory containing supporting files) then it can be useful to use
661 C<$talkdir> to refer back to the original directory.
662
663 =head1 WHAT MAKES A GOOD TALK
664
665 I like what Edward Tufte writes, for example his evisceration of
666 PowerPoint use at NASA here:
667 L<http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0001yB>
668
669 However it is sometimes hard to translate his ideas into clear
670 presentations, and not all of that is the fault of the tools.  Here
671 are my thoughts and rules on how to deliver a good talk.
672
673 B<First, most important rule:> Before you start drawing any slides at
674 all, write your talk as a short essay.
675
676 This is the number one mistake that presenters make, and it is partly
677 a tool fault, because PowerPoint, OpenOffice, even Tech Talk PSE, all
678 open up on an initial blank slide, inviting you to write a title and
679 some bullet points.  If you start that way, you will end up using the
680 program as a kind of clumsy outlining tool, and then reading that
681 outline to your audience.  That's boring and a waste of time for you
682 and your audience.  (It would be quicker for them just to download the
683 talk and read it at home).
684
685 B<Secondly:> How long do you want to spend preparing the talk?  A good
686 talk, with a sound essay behind it, well thought out diagrams and
687 figures, and interesting demonstrations, takes many hours to prepare.
688 How many hours?  I would suggest thinking about how many hours of
689 effort your audience are putting in.  Even just 20 people sitting
690 there for half an hour is 10 man-hours of attention, and that is a
691 very small talk, and doesn't include all the extra time and hassle
692 that it took to get them all in one place.
693
694 I don't think you can get away with spending less than two full days
695 preparing a talk, if you want to master the topic and draw up accurate
696 slides.  Steve Jobs is reputed to spend weeks preparing his annual
697 sales talk to the Apple faithful.
698
699 B<Thirdly:> Now that you're going to write your talk as an essay, what
700 should go in the slides?  I would say that you should consider
701 delivering the essay, I<not> the slides, to people who don't make the
702 talk.  An essay can be turned into an article or blog posting, whereas
703 even "read-out-the-bullet-point" slides have a low information
704 density, large size, and end-user compatibility problems (*.pptx
705 anyone?).
706
707 What, then, goes on the slides?  Anything you cannot just say:
708 diagrams, graphs, videos, animations, and of course (only with Tech
709 Talk PSE!) demonstrations.
710
711 B<Lastly:> Once you've got your talk as an essay and slides, practice,
712 practice and practice again.  Deliver the talk to yourself in the
713 mirror, to your colleagues.  Practice going backwards and forwards
714 through the slides, using your actual laptop and the software so you
715 know what to click and what keys to press.  Partly memorize what you
716 are going to say (but use short notes written on paper if you need
717 to).
718
719 =head1 SEE ALSO
720
721 The Cognitive Style of PowerPoint, Tufte, Edward R.
722
723 =head1 AUTHOR
724
725 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
726
727 =head1 COPYRIGHT
728
729 Copyright (C) 2010 Red Hat Inc.
730
731 This program is free software; you can redistribute it and/or modify
732 it under the terms of the GNU General Public License as published by
733 the Free Software Foundation; either version 2 of the License, or
734 (at your option) any later version.
735
736 This program is distributed in the hope that it will be useful,
737 but WITHOUT ANY WARRANTY; without even the implied warranty of
738 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
739 GNU General Public License for more details.
740
741 You should have received a copy of the GNU General Public License
742 along with this program; if not, write to the Free Software
743 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.