=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:
+=head2 CREATING A NEW TALK
+
+Tech Talk PSE talks are just directories containing C<*.html> and
+C<*.sh> (shell script) files:
+
+ 0010-introduction.html
+ 0500-demonstration.sh
+ 9900-conclusion.html
+
+The filenames that Tech Talk PSE considers to be slides have to match
+the regular expression:
+
+ ^(\d+)(?:-.*)\.(html|sh)$
+
+(any other file or subdirectory is ignored). Shell scripts I<must>
+be executable.
+
+You can create a new talk just by creating an empty directory and
+adding files as above, but you can also create a useful skeleton talk
+like this:
+
+ mkdir talk
+ cd talk
+ techtalk-pse --new
+
+The C<--new> flag will refuse to overwrite any existing files, so you
+should run it in an empty directory.
+
+=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
=cut
+my $new;
+
+=item B<--new>
+
+Create a new outline talk in an empty directory.
+
+This refuses to overwrite existing files.
+
+=cut
+
my $start;
=item B<-n SLIDE> | B<--start SLIDE>
=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,
+ "new" => \$new,
"splash!" => \$splash,
"start=s" => \$start,
"verbose" => \$verbose,
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;
+die "techtalk-pse: too many arguments\n" if !$mozembed && @ARGV >= 2;
# Get the true name of the program.
$0 = abs_path ($0);
my $talkdir = getcwd;
$ENV{talkdir} = $talkdir;
+# Create a new talk (--new flag).
+if ($new) {
+ my %files = (
+ "essay.txt" => {
+ mode => 0644,
+ desc => "essay and background notes",
+ c => 'Start by writing your thoughts in this file as an essay.
+
+You can then provide this as extra background reading material
+for your audience after the talk.'
+ },
+
+ "0010-introduction.html" => {
+ mode => 0644,
+ desc => "title slide",
+ c => '<link rel="stylesheet" href="style.css" type="text/css"/>
+<script src="code.js" type="text/javascript"></script>
+
+<div class="titlepage">
+<p>A Technical Talk</p>
+<author>by John Smith (jsmith@example.com)</author>
+</div>'
+ },
+
+ "0500-demonstration.html" => {
+ mode => 0644,
+ desc => "intro to demonstration slide",
+ c => '<link rel="stylesheet" href="style.css" type="text/css"/>
+<script src="code.js" type="text/javascript"></script>
+
+<h1>Demonstration</h1>
+
+<p>The next slide demonstrates a gnome terminal.</p>
+
+<p>Hit the UP arrow to access the preloaded history.</p>'
+ },
+
+ "0510-demonstration.sh" => {
+ mode => 0755,
+ desc => "shell demonstration slide",
+ c => '#!/bin/bash -
+source functions
+add_history ls -l
+add_history virt-df -a
+terminal --title="Demonstration terminal"'
+ },
+
+ "9900-conclusion.html" => {
+ mode => 0644,
+ desc => "last slide",
+ c => '<link rel="stylesheet" href="style.css" type="text/css"/>
+<script src="code.js" type="text/javascript"></script>
+
+<h1>Conclusions</h1>
+
+<p>The conclusion page</p>'
+ },
+
+ "functions" => {
+ mode => 0644,
+ desc => "shell script helper functions",
+ c => '# -*- shell-script -*-
+
+# Place any local environment variables and settings in "local".
+if [ -f local ]; then source local; fi
+
+export PS1=\'\\$ \'
+
+export HISTFILE=$talkdir/history
+
+rm -f $HISTFILE
+touch $HISTFILE
+
+add_history ()
+{
+ echo "$@" >> $HISTFILE
+}
+
+# Note: If you hand-configure gnome-terminal by adding a
+# new profile (eg. with larger fonts) then you can use that
+# profile here by replacing the --window flag with
+# --window-with-profile=ProfileName
+
+terminal ()
+{
+ chmod -w $HISTFILE
+ exec \\
+ gnome-terminal \\
+ --window \\
+ --geometry=+140+64 \\
+ --hide-menubar \\
+ --disable-factory \\
+ -e \'/bin/bash --norc\' \\
+ "$@"
+}
+'
+ },
+
+ "style.css" => {
+ mode => 0644,
+ desc => "HTML stylesheet",
+ c => 'body {
+ font-size: 28pt;
+ font-family: liberation, helvetica;
+}
+
+h1 {
+ font-size: 48px;
+ top: 8;
+ left: 0;
+ border-bottom: 2px solid #ccc;
+}
+
+div.titlepage {
+ margin-top: 100px;
+ text-align: center;
+}
+'
+ },
+ );
+
+ # Refuse to overwrite existing files.
+ foreach (sort keys %files) {
+ die "techtalk-pse: refusing to overwrite '$_'\n" if -f $_;
+ }
+
+ # Write the files.
+ foreach (sort keys %files) {
+ print "writing $_ ($files{$_}{desc}) ...\n";
+ open FILE, ">$_" or die "$_: open: $!";
+ print FILE $files{$_}{c} or die "$_: print: $!";
+ close FILE or die "$_: close: $!";
+ chmod $files{$_}{mode}, $_ or die "$_: chmod: $!";
+ }
+
+ exit 0;
+}
+
# Get the files.
my @files;
my %files;
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) {
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 {
# 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 $url = "file://$talkdir/" . $slide->{name};
- push @cmd, $url;
+ # 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;
- 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";
+ 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") {
$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);
+ 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);
# killing the whole program.
sub run_mozembed
{
- my $r = 0;
-
my $w = Gtk2::Window->new ();
my $vbox = Gtk2::VBox->new ();
my $moz = Gtk2::MozEmbed->new ();
- my $bbox = Gtk2::HButtonBox->new ();
- $bbox->set_layout ('start');
+ 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->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 ();
- $moz->load_url ($ARGV[0]);
+ $moz->load_url ($url);
+
Gtk2->main;
- exit $r;
+ 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
L</WHAT MAKES A GOOD TALK> below].
To start your talk, all you have to do is to make a new directory
-somewhere:
+somewhere and run Tech Talk PSE with the C<--new> flag to create an
+outline:
mkdir talk
cd talk
+ techtalk-pse --new
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
+description, followed by the extension (C<.html> or C<.sh>).
To run it, run the command from within the talk directory:
In C<functions>, 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 ()
{
terminal ()
{
+ # Make $HISTFILE unwritable so the shell won't update it
+ # when it exits.
+ chmod -w $HISTFILE
+
+ # Run gnome-terminal.
exec \
gnome-terminal \
--window \