Skip to content

Instantly share code, notes, and snippets.

@bazzargh
Created November 13, 2015 21:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bazzargh/a267b97a52f7a1f70c46 to your computer and use it in GitHub Desktop.
Save bazzargh/a267b97a52f7a1f70c46 to your computer and use it in GitHub Desktop.
hackertyper style presentation tool, leverages ttyrec to let you play back the presentation from random keystrokes
#!/usr/bin/env ruby
# hackertyper-style playback of series of tty recordings created by
# ttyrec.
#
# usage:
# hacker slides/*
#
# - Type randomly to move forward inside file
# - backspace to skip back or prev file
# - return to skip ahead or next file
# - Ctrl-C to exit
# - plays a bell if you go too far (I set term up with a visual bell)
#
# record with eg:
# ttyrec -e 'bash -l' slide9
#
# bash -l means you get your real prompt. This tries to keep together
# events that occurred within 20ms. So for best results, pause after
# typing each command when recording, THEN press return.
#
# I'd suggest naming files like slides/07_testing rather than eg
# slide01. This means they're easily globbed (slide/*) and are sorted,
# but the names are meaningful so you can move them around without
# having to peek inside all the time. Name your last slide 99_end to
# avoid renumbering as the number of slides changes.
require 'io/console'
class Slide
def initialize
@slides = []
ARGV.each do |filename|
File.open(filename, 'rb') do |f|
@slides << parse(f)
end
end
set_slide(0)
end
def set_slide(s)
clear
@step = 0
return if s == @slide
@slide = [0, [s, @slides.length - 1].min].max
@steps = @slides[@slide]
end
def run
begin
# save previous state of stty
old_state = `stty -g`
# ~(ICANON | ECHO | ECHONL)
system "stty -icanon -echo -echonl"
loop do
c = STDIN.getch
case c
when "\u007F" # backspace
backward
when "\r" # return
forward
when "\u0003" # ^C.
break
else
next_step
end
end
rescue => ex
puts "#{ex.class}: #{ex.message}"
puts ex.backtrace
ensure
# restore previous state of stty
system "stty #{old_state}"
end
end
def forward
if last_step?
if last_slide?
flash
else
set_slide(@slide + 1)
end
else
while !last_step?
next_step
end
end
end
def backward
if first_step?
if first_slide?
flash
else
set_slide(@slide - 1)
end
else
set_slide(@slide)
end
end
def next_step
# dump out whatever the next command sequence is
if last_step?
flash
else
begin
$stdout.write @steps[@step]
rescue e
@error = e
end
@step = @step + 1
end
end
def first_step?
@step == 0
end
def last_step?
@step == @steps.length - 1
end
def first_slide?
@slide == 0
end
def last_slide?
@slide == @slides.length - 1
end
def flash
print "\a"
end
def clear
puts `clear`
@step = 0
end
def parse(recording)
slide = []
# Anything in the same, say, 1/20s should be played
# as a single event.
pending_data = nil
while !recording.eof?
sec, usec, len = recording.read(12).unpack('VVV')
t = "#{sec}.#{usec}".to_f
data = recording.read(len)
if pending_data.nil?
pending_data = data
tstart = t
elsif (t - tstart > 0.05)
slide << pending_data
pending_data = data
tstart = t
else
pending_data << data
end
end
if !pending_data.nil?
slide << pending_data
end
slide
end
end
puts Slide.new.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment