X-Git-Url: http://git.annexia.org/?p=techtalk-pse.git;a=blobdiff_plain;f=techtalk-pse.pl;h=1b38a9aaea39d3a2c9aa28d49ba3da2323fd8a99;hp=ae8a9c9a1230113a245d7f3441f2c08d6955363a;hb=6035f405c917a547f550e3001e60b04da5724da9;hpb=ccd8fffc4ae378f86dc399ea6eefec2f7f9ad8ed diff --git a/techtalk-pse.pl b/techtalk-pse.pl index ae8a9c9..1b38a9a 100755 --- a/techtalk-pse.pl +++ b/techtalk-pse.pl @@ -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 @@ -29,7 +29,9 @@ use Getopt::Long; use Cwd qw(getcwd abs_path); use Glib qw(TRUE FALSE); use Gtk2 -init; -use Gtk2::MozEmbed; +use Gtk2::Gdk::Keysyms; +use Gtk2::WebKit; +use Gnome2::Vte; =encoding utf8 @@ -67,10 +69,26 @@ there is a discussion on L. =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 section -below). To display or run the talk, change into the directory -containing all those files and run the C 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 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 command: cd /path/to/talk/; techtalk-pse @@ -116,15 +134,6 @@ The default is to start at the first slide in the talk. =cut -my $splash = 1; - -=item B<--no-splash> - -Don't display the initial "splash" screen which advertises Tech Talk -PSE to your audience. Just go straight into the talk. - -=cut - my $verbose; =item B<--verbose> @@ -142,17 +151,9 @@ Display version number and exit. =cut -my $mozembed; -my $mozembed_first; -my $mozembed_last; - GetOptions ("help|?" => \$help, "last" => \$last, - "mozembed" => \$mozembed, - "mozembed-first" => \$mozembed_first, - "mozembed-last" => \$mozembed_last, "n=s" => \$start, - "splash!" => \$splash, "start=s" => \$start, "verbose" => \$verbose, "version" => \$version, @@ -170,10 +171,6 @@ if ($version) { die "techtalk-pse: cannot use --start and --last options together\n" if defined $last && defined $start; -# Run with --mozembed: see below. -run_mozembed () if $mozembed; - -# Normal run of the program. die "techtalk-pse: too many arguments\n" if @ARGV >= 2; # Get the true name of the program. @@ -191,22 +188,143 @@ 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 $current; +my $pid; +my $pipeline; + +&reread_directory (); + +print STDERR "read ", 0+@files, " files\n" if $verbose; +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 $splash = make_splash_page (); +my $emptylabel = Gtk2::Label->new (); + +my $webkitscroll = Gtk2::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); + +my ($bbox, $bquit, $breload, $bnext, $bback, $brestart) = make_button_bar (); + +$vbox->pack_start($bbox, 0, 0, 0); +$vbox->pack_start($notebook, 1, 1, 0); + +$notebook->set_show_tabs(0); +$notebook->set_show_border(0); + +# Default font size is almost certainly too small +# for audience to see. +# XXX we should make font size configurable via +# @ARGV. +# XXX any way we can scale WebKit programmatically +# to set base size which CSS is relative to ? +# 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); + +# When an external command exits, automatically +# go to the next slide +$vte->signal_connect ( + 'child-exited' => sub { + if ($pid) { + $pid = 0; + &switch_slide("NEXT"); + } + }); + +# Exit if the window is closed +$w->signal_connect ( + destroy => sub { + Gtk2->main_quit; + return FALSE; + }); + +# Handle left/right arrows, page up/down & home/end +# as slide navigation commands. But not when there +# is a shell running +$w->signal_connect ( + 'key-press-event' => sub { + my $src = shift; + my $ev = shift; + + # If a shell is running, don't trap keys + if ($pid) { + return 0; + } + + if ($ev->keyval == $Gtk2::Gdk::Keysyms{Right} || + $ev->keyval == $Gtk2::Gdk::Keysyms{Page_Down}) { + &switch_slide("NEXT"); + return 1; + } elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{Left} || + $ev->keyval == $Gtk2::Gdk::Keysyms{Page_Up}) { + &switch_slide("PREV"); + return 1; + } elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{Home}) { + &switch_slide("FIRST"); + return 1; + } elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{End}) { + &switch_slide("LAST"); + return 1; + } elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{q} || + $ev->keyval == $Gtk2::Gdk::Keysyms{Escape}) { + Gtk2->main_quit; + return 1; + } + return 0; + }); + + +$w->add ($vbox); + +# This allows us to resize the window in window_in_corner(). +$w->set_geometry_hints ($w, { min_width => 100 }, qw(min-size)); + +$w->show_all (); + +window_fullscreen (); + +&update_slide(); + +Gtk2->main(); + +exit 0; + sub reread_directory { @files = (); 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; @@ -221,222 +339,309 @@ sub reread_directory $files[0]->{first} = 1; $files[$#files]->{last} = 1; } + + # Work out what slide we're starting on. + if (@files && !$current) { + if ($start) { + foreach my $file (@files) { + if ($file->{name} =~ /^$start/) { + $current = $file; + last; + } + } + } elsif ($last) { + $current = $files[$#files]; + } + if (!$current) { + $current = $files[0]; + } + } } -reread_directory (); -print STDERR "read ", 0+@files, " files\n" if $verbose; -if (@files == 0) { - warn "techtalk-pse: no files found, continuing anyway ...\n" + +sub window_fullscreen +{ + $w->set_decorated (0); + $w->fullscreen (); + $w->move (0,0); } -# Work out what slide we're starting on. -my $current; -if (defined $current) { - die "start slide not implemented yet XXX" +sub window_in_corner +{ + $w->set_decorated (1); + $w->unfullscreen (); + + my $root = Gtk2::Gdk->get_default_root_window (); + my ($width, $height) = $root->get_size; + + $w->resize ($width/2, $height/4); + $w->move ($width/2, 64); } -elsif (@files) { - $current = $files[0]; + +sub run_process +{ + $pid = $vte->fork_command("./" . $current->{name}, [], [], undef, 0, 0, 0); } -# else $current is undefined - -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" - ); - $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->run; - print STDERR "calling \$w->destroy on about dialog\n" if $verbose; - $w->destroy; + +sub kill_process +{ + print STDERR "sending TERM signal to process group $pid\n" + if $verbose; + kill "TERM", -$pid; + + # Clears out any current displayed text + $vte->reset(1, 1); + $vte->set_default_colors(); + $pid = 0; } -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; - $current = $files[$i]; - } +sub switch_slide +{ + my $action = shift; + + window_fullscreen (); + + if ($pid) { + kill_process (); + } + if ($pipeline) { + $pipeline->set_state('ready'); + $pipeline = undef; + } + print STDERR "action = $action\n" if $verbose; + + my $i = defined $current ? $current->{i} : 0; + + print STDERR "i = $i\n" if $verbose; + if ($action eq "PREV") { + if (defined $current) { + $i--; + } else { + $i = $#files; + } + } elsif ($action eq "NEXT") { + $i++; + } elsif ($action eq "FIRST") { + $i = 0; + } elsif ($action eq "LAST") { + $i = $#files; + } elsif ($action =~ /^I_(\d+)$/) { + $i = $1; + } + + $i = 0 if $i < 0; + if ($i > $#files) { + $current = undef; } else { - print "No slides found. Press any key to reload directory ...\n"; - $_ = ; + $current = $files[$i]; } - # Reread directory between slides. - reread_directory (); + &update_slide (); - if (defined $current && !exists $files{$current->{name}}) { - # Current slide was deleted. - undef $current; - $current = $files[0] if @files; - } } -sub show_slide +sub update_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 @cmd = ($0, "--mozembed"); - push @cmd, "--mozembed-first" if exists $slide->{first}; - push @cmd, "--mozembed-last" if exists $slide->{last}; - my $cwd = getcwd; - my $url = "file://" . $cwd . "/" . $slide->{name}; - push @cmd, $url; - system (@cmd); - die "failed to execute subcommand: ", join(" ", @cmd), ": $!\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"; - } - } + if ($current) { + # Display an HTML page. + if ($current->{ext} eq "html") { + $notebook->set_current_page ($webkitpage); + my $name = $current->{name}; + my $url = "file://$talkdir/$name"; + + $webkit->load_uri ($url); + $webkit->grab_focus (); + } + # 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 (); + } + } else { + $notebook->set_current_page ($splashpage); } - # 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 = Gtk2::HButtonBox->new (); - $bbox->set_layout ('start'); - - my $bnext = Gtk2::Button->new ("Next slide"); - $bnext->signal_connect (clicked => sub { $r = "NEXT"; $w->destroy }); - $bnext->set_sensitive (!(exists $slide->{last})); - $bbox->add ($bnext); - - my $bback = Gtk2::Button->new ("Back"); - $bback->signal_connect (clicked => sub { $r = "PREV"; $w->destroy }); - $bback->set_sensitive (!(exists $slide->{first})); - $bbox->add ($bback); - - my $brestart = Gtk2::Button->new ("Kill & restart"); - $brestart->signal_connect (clicked => sub { - kill_process (); - run_process (); - }); - $bbox->add ($brestart); - - my $bquit = Gtk2::Button->new ("Quit"); - $bquit->signal_connect (clicked => sub { $r = "QUIT"; $w->destroy }); - $bbox->add ($bquit); - $bbox->set_child_secondary ($bquit, 1); - - $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 ($pid) { + $brestart->show (); + } else { + $brestart->hide (); } + + if (defined $current) { + $bquit->hide (); + $breload->hide (); + $bnext->set_sensitive (1); + $bback->set_sensitive (!exists $current->{first}); + } else { + $bquit->show (); + if (@files) { + $breload->hide (); + } else { + $breload->show (); + } + $bnext->set_sensitive (0); + $bback->set_sensitive (int(@files)); + } +} + + +sub make_splash_page { + my $box = Gtk2::VBox->new(); + + my $title = Gtk2::Label->new ("Tech Talk Platinum Supreme Edition (PSE)"); + $title->set_use_markup (1); + + $box->pack_start ($title, 0, 1, 0); + + my $vers = Gtk2::Label->new ("@VERSION@"); + $vers->set_use_markup (1); + $box->pack_start ($vers, 0, 1, 0); + + my $tagline = Gtk2::Label->new ("Superior technical demonstration software"); + $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); + + my $url = Gtk2::Label->new ("http;//people.redhat.com/~rjones/"); + $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); + + return $box; } -# 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 +# Make the standard button bar across the top of the page. +sub make_button_bar { - my $r = 0; + my $bbox = Gtk2::Toolbar->new (); + $bbox->set_style ("GTK_TOOLBAR_TEXT"); - my $w = Gtk2::Window->new (); - my $vbox = Gtk2::VBox->new (); - my $moz = Gtk2::MozEmbed->new (); + my $i = 0; - my $bbox = Gtk2::HButtonBox->new (); - $bbox->set_layout ('start'); + my $bquit = Gtk2::ToolButton->new (undef, "Quit"); + $bquit->signal_connect (clicked => sub { Gtk2->main_quit }); + $bbox->insert ($bquit, $i++); + + my $breload = Gtk2::ToolButton->new (undef, "Reload"); + $breload->signal_connect (clicked => sub { reread_directory () }); + $bbox->insert ($breload, $i++); + + my $bnext = Gtk2::ToolButton->new (undef, "Next slide"); + $bnext->signal_connect (clicked => sub { &switch_slide ("NEXT") }); + $bbox->insert ($bnext, $i++); + + my $bback = Gtk2::ToolButton->new (undef, "Back"); + $bback->signal_connect (clicked => sub { &switch_slide ("PREV") }); + $bbox->insert ($bback, $i++); + + $bbox->insert (Gtk2::SeparatorToolItem->new (), $i++); + + my $brestart = Gtk2::ToolButton->new (undef, "Kill & restart"); + $brestart->signal_connect (clicked => + sub { + kill_process (); + run_process (); + }); + $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 $mfirst = Gtk2::MenuItem->new ("First slide"); + $mfirst->signal_connect (activate => sub { &switch_slide ("FIRST") }); + $mfirst->show (); + $optsmenu->append ($mfirst); + + my $mlast = Gtk2::MenuItem->new ("Last slide"); + $mlast->signal_connect (activate => sub { &switch_slide ("LAST") }); + $mlast->show (); + $optsmenu->append ($mlast); + + my $slidesmenu = Gtk2::Menu->new (); + foreach (@files) { + my $item = Gtk2::MenuItem->new ($_->{name}); + my $index = $_->{i}; + $item->signal_connect (activate => sub { &switch_slide ("I_$index") }); + $item->set_sensitive ($current->{i} != $index); + $item->show (); + $slidesmenu->append ($item); + } - $vbox->pack_start ($bbox, 0, 0, 0); - $vbox->add ($moz); - $w->fullscreen (); - #$w->set_default_size (640, 480); - $w->add ($vbox); - - my $bnext = Gtk2::Button->new ("Next slide"); - $bnext->signal_connect (clicked => sub { $r = 0; $w->destroy }); - $bnext->set_sensitive (!$mozembed_last); - $bbox->add ($bnext); - - my $bback = Gtk2::Button->new ("Back"); - $bback->signal_connect (clicked => sub { $r = 1; $w->destroy }); - $bback->set_sensitive (!$mozembed_first); - $bbox->add ($bback); - - my $bquit = Gtk2::Button->new ("Quit"); - $bquit->signal_connect (clicked => sub { $r = 2; $w->destroy }); - $bbox->add ($bquit); - $bbox->set_child_secondary ($bquit, 1); - - $w->signal_connect (destroy => sub { - Gtk2->main_quit; - return FALSE; - }); - $w->show_all (); + my $mslides = Gtk2::MenuItem->new ("Slides"); + $mslides->set_submenu ($slidesmenu); + $mslides->show (); + $optsmenu->append ($mslides); + + my $sep2 = Gtk2::SeparatorMenuItem->new (); + $sep2->show (); + $optsmenu->append ($sep2); + + my $mscreenshot = Gtk2::MenuItem->new ("Take a screenshot"); + $mscreenshot->signal_connect (activate => sub { screenshot () }); + $mscreenshot->show (); + $optsmenu->append ($mscreenshot); + + my $sep3 = Gtk2::SeparatorMenuItem->new (); + $sep3->show (); + $optsmenu->append ($sep3); + + my $mquit = Gtk2::MenuItem->new ("Quit"); + $mquit->signal_connect (activate => sub { Gtk2->main_quit }); + $mquit->show (); + $optsmenu->append ($mquit); + + my $moptions = Gtk2::MenuToolButton->new (undef, "Options"); + #$boptions->signal_connect (clicked => + # sub { $optsmenu->popup (undef, undef, undef, undef, ?, ?) } ); + $bbox->insert ($moptions, $i++); + $moptions->set_menu ($optsmenu); + + 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. +sub screenshot +{ + system ("gnome-screenshot"); - $moz->load_url ($ARGV[0]); - Gtk2->main; + if ($? == -1) { + # We are going to save the entire screen. + my $root = Gtk2::Gdk->get_default_root_window (); + my ($width, $height) = $root->get_size; - exit $r; + # 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 @@ -452,8 +657,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 @@ -521,9 +726,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 @@ -533,8 +739,8 @@ an CimgE> tag, eg: -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. @@ -563,9 +769,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: @@ -580,9 +791,16 @@ 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=/tmp/history + + export HISTFILE=$talkdir/history + rm -f $HISTFILE + touch $HISTFILE add_history () { @@ -591,29 +809,25 @@ In C, I have: terminal () { - exec \ - gnome-terminal \ - --window \ - --geometry=+100+100 \ - --hide-menubar \ - --disable-factory \ - -e '/bin/bash --norc' \ - "$@" + # Make $HISTFILE unwritable so the shell won't update it + # when it exits. + chmod -w $HISTFILE + + # 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 @@ -623,7 +837,7 @@ 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)$ + ^(\d+)(?:-.*)\.(html|sh|term)$ For future compatibility, you should ensure that every slide has a unique numeric part (ie. I have C<0010-aaa.html> and @@ -651,6 +865,13 @@ 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 @@ -684,7 +905,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 Now that you're going to write your talk as an essay, what