--- /dev/null
+#!/usr/local/bin/perl5 -w
+#
+# webcollage, for xscreensaver, Copyright (c) 1999 Jamie Zawinski <jwz@jwz.org>
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation. No representations are made about the suitability of this
+# software for any purpose. It is provided "as is" without express or
+# implied warranty.
+#
+#
+# This program decorate the screen with random images from the web.
+
+
+use Socket;
+
+my $progname = "$0";
+my $version = "1.0";
+
+$progname =~ s@^.*/([^/]+)$@$1@;
+
+my $random_redirector = "http://random.yahoo.com/bin/ryl";
+my $image_randomizer_a = "http://image.altavista.com/";
+my $image_randomizer = $image_randomizer_a . "cgi-bin/avncgi" .
+ "?do=3&verb=no&oshape=n&oorder=" .
+ "&ophoto=1&oart=1&ocolor=1&obw=1" .
+ "&stype=simage&oprem=0&query=";
+
+my $http_timeout = 30;
+my $ppm_to_root_window_cmd = "xv -root -rmode 5 -viewonly" .
+ " +noresetroot %%PPM%% -quit";
+my $filter_cmd = undef;
+my $post_filter_cmd = undef;
+my $background = undef;
+my $no_output_p = 0;
+my $delay = 0;
+
+my $wordlist = "/usr/dict/words";
+
+if (!-r $wordlist) {
+ $wordlist = "/usr/share/lib/dict/words"; # irix
+}
+
+
+my $min_width = 50;
+my $min_height = 50;
+
+my $DEBUG = 0;
+
+
+# returns three values: the HTTP response line; the document headers;
+# and the document body.
+#
+sub get_document_1 {
+ my ( $url ) = @_;
+
+ if ( $DEBUG > 2 ) {
+ print STDERR "get_document_1 $url\n";
+ }
+
+ my($dummy, $dummy, $serverstring, $path) = split(/\//, $url, 4);
+ my($them,$port) = split(/:/, $serverstring);
+ $port = 80 unless $port;
+ my $size="";
+
+ my ($remote, $iaddr, $paddr, $proto, $line);
+ $remote = $them;
+ if ($port =~ /\D/) { $port = getservbyname($port, 'tcp') }
+ return unless $port;
+ $iaddr = inet_aton($remote) || return;
+ $paddr = sockaddr_in($port, $iaddr);
+
+ @_ =
+ eval {
+ local $SIG{ALRM} = sub {
+ if ($DEBUG > 0) {
+ print STDERR "timed out for $url\n";
+ }
+ die "alarm\n" };
+ alarm $http_timeout;
+
+ $proto = getprotobyname('tcp');
+ socket(S, PF_INET, SOCK_STREAM, $proto) || return;
+ connect(S, $paddr) || return;
+
+ select(S); $| = 1; select(STDOUT);
+
+ print S ("GET /$path HTTP/1.0\n" .
+ "Host: $them\n" .
+ "User-Agent: $progname/$version\n" .
+ "\n");
+
+ my $http = <S>;
+
+ my $head = "";
+ my $body = "";
+ while (<S>) {
+ $head .= $_;
+ last if m@^[\r\n]@;
+ }
+ while (<S>) {
+ $body .= $_;
+ }
+
+ close S;
+
+ return ( $http, $head, $body );
+ };
+ die if ($@ && $@ ne "alarm\n"); # propagate errors
+ if ($@) {
+ # timed out
+ return undef;
+ } else {
+ # didn't
+ alarm 0;
+ return @_;
+ }
+}
+
+
+# returns two values: the document headers; and the document body.
+# if the given URL did a redirect, returns the redirected-to document.
+#
+sub get_document {
+ my ( $url ) = @_;
+
+ do {
+ my ( $http, $head, $body ) = get_document_1 $url;
+
+ return undef if ( ! $body );
+
+ if ( $http =~ m@HTTP/[0-9.]+ 30[23]@ ) {
+ $_ = $head;
+ my ( $location ) = m@^location:[ \t]*(.*)$@im;
+ if ( $location ) {
+
+ if ( $DEBUG > 2 ) {
+ print STDERR "redirect from $url to $location\n";
+ }
+ $url = $location;
+ } else {
+ return ( $url, $body );
+ }
+
+ } elsif ( $http =~ m@HTTP/[0-9.]+ [4-9][0-9][0-9]@ ) {
+ # http errors -- return nothing.
+ return undef;
+
+ } else {
+
+ return ( $url, $body );
+ }
+
+ } while (1);
+}
+
+
+# given a URL and the body text at that URL, selects and returns a random
+# image from it. returns undef if no suitable images found.
+#
+sub pick_image_from_body {
+ my ( $base, $body ) = @_;
+
+ $_ = $base;
+
+ # if there's at least one slash after the host, take off the last
+ # pathname component
+ if ( m@^http://[^/]+/@io ) {
+ ( $base = $base ) =~ s@[^/]+$@@go;
+ }
+
+ # if there are no slashes after the host at all, put one on the end.
+ if ( m@^http://[^/]+$@io ) {
+ $base .= "/";
+ }
+
+ if ( $DEBUG > 2 ) {
+ print STDERR "base is $base\n";
+ }
+
+
+ $_ = $body;
+
+ # strip out newlines, compress whitespace
+ s/[\r\n\t ]+/ /go;
+
+ # nuke comments
+ s/<!--.*?-->//go;
+
+ my @urls;
+ my %unique_urls;
+
+ foreach (split(/ *</)) {
+ if ( m/^(img|a) .*(src|href) ?= ?\"? ?(.*?)[ >\"]/io ) {
+
+ my $was_inline = ( "$1" eq "a" || "$1" eq "A" );
+ my $link = $3;
+ my ( $width ) = m/width ?= ?([0-9]+)/oi;
+ my ( $height ) = m/height ?= ?([0-9]+)/oi;
+ $_ = $link;
+
+ if ( m@^/@o ) {
+ my $site;
+ ( $site = $base ) =~ s@^(http://[^/]*).*@$1@gio;
+ $_ = "$site$link";
+ } elsif ( ! m/:/ ) {
+ $_ = "$base$link";
+ s@/\./@/@;
+ while (s@/\.\./@/@g) {
+ }
+ }
+
+ # skip non-http
+ if ( ! m@^http://@io ) {
+ next;
+ }
+
+ # skip non-image
+ if ( ! m@[.](gif|jpg|jpeg|pjpg|pjpeg)@io ) {
+ next;
+ }
+
+ # skip GIF!
+# if ( m@[.](gif)@io ) {
+## if ( $DEBUG > 2 ) { print "skip GIF $_\n"; }
+# next;
+# }
+
+ # skip really short or really narrow images
+ if ( $width && $width < $min_width) {
+ if ( $DEBUG > 2 ) {
+ print STDERR "skip narrow image $_ ($width x $height)\n";
+ }
+ next;
+ }
+
+ if ( $height && $height < $min_height) {
+ if ( $DEBUG > 2 ) {
+ print STDERR "skip short image $_ ($width x $height)\n";
+ }
+ next;
+ }
+
+ my $url = $_;
+
+ if ( $unique_urls{$url} ) {
+ if ( $DEBUG > 2 ) { print STDERR "skip duplicate image $_\n"; }
+ next;
+ }
+
+ if ( $DEBUG > 2 ) {
+ print STDERR "got $url" .
+ ($width && $height ? " (${width}x${height})" : "") .
+ ($was_inline ? " (inline)" : "") . "\n";
+ }
+
+ $urls[++$#urls] = $url;
+ $unique_urls{$url}++;
+
+ # pointers to images are preferable to inlined images
+ if ( ! $was_inline ) {
+ $urls[++$#urls] = $url;
+ }
+ }
+ }
+
+ if ( $#urls == 0 ) {
+ if ( $DEBUG > 2 ) {
+ print STDERR "no images on $base\n";
+ }
+ return undef;
+ }
+
+ return undef if ( $#urls < 1 );
+
+ # pick a random element of the table
+ my $i = ((rand() * 99999) % $#urls);
+
+ # if the page has several images on it, prefer the later ones most of
+ # the time.
+ my $fudge = 4;
+ if ($#urls > ($fudge * 2) && $i <= $fudge && ((rand() < 0.9))) {
+ if ( $DEBUG > 2 ) {
+ print STDERR "skipping first $fudge of $#urls images.\n";
+ }
+ $i += ($fudge - $i);
+ }
+
+ my $url = $urls[$i];
+
+ if ( $DEBUG > 2 ) {
+ print STDERR "picked $url\n";
+ }
+
+ return $url;
+}
+
+
+# Using the URL-randomizer, picks a random image on a random page, and
+# returns two URLs: the page containing the image, and the image.
+# Returns undef if nothing found this time.
+#
+sub pick_from_url_randomizer {
+
+ if ( $DEBUG > 2 ) {
+ print STDERR "\n\npicking from $random_redirector...\n\n";
+ }
+
+ my ( $base, $body ) = get_document $random_redirector;
+
+ return if (!$base || !$body);
+ my $img = pick_image_from_body ($base, $body);
+
+ if ($img) {
+ return ($base, $img);
+ } else {
+ return undef;
+ }
+}
+
+
+sub random_word {
+
+ my $word = 0;
+ if (open (IN, "<$wordlist")) {
+ my $size = (stat(IN))[7];
+ my $pos = rand $size;
+ if (seek (IN, $pos, 0)) {
+ $word = <IN>; # toss partial line
+ $word = <IN>; # keep next line
+ }
+ close (IN);
+ }
+
+ return 0 if (!$word);
+
+ $word =~ s/^[ \t\n\r]+//;
+ $word =~ s/[ \t\n\r]+$//;
+ $word =~ s/ly$//;
+ $word =~ s/ies$/y/;
+ $word =~ s/ally$/al/;
+
+ return $word;
+}
+
+
+
+# Using the image-randomizer, picks a random image on a random page, and
+# returns two URLs: the page containing the image, and the image.
+# Returns undef if nothing found this time.
+#
+sub pick_from_image_randomizer {
+
+ my $words = random_word;
+ $words .= "%20" . random_word;
+ $words .= "%20" . random_word;
+
+ my $search_url = $image_randomizer . $words;
+
+ if ( $DEBUG > 2 ) {
+ print STDERR "\n\npicking from $search_url\n";
+ }
+
+ my ( $base, $body ) = get_document $search_url;
+
+ return if (! $body);
+
+
+ my @subpages;
+ my $skipped = 0;
+
+ $_ = $body;
+ s/(<A )/\n$1/gi;
+ foreach (split(/\n/)) {
+
+ if ( m@<A HREF=([^>]+)><IMG SRC=http://image\.altavista\.com@i ) {
+
+ my $u = $1;
+ if (m/^"(.*)"$/) { $u = $1; }
+
+ if (m@\.corbis\.com/@) {
+ $skipped = 1;
+ if ( $DEBUG > 2 ) {
+ print STDERR "skipping corbis URL: $_\n";
+ }
+ next;
+ } elsif ( $DEBUG > 2 ) {
+ print STDERR "sub-page: $1\n";
+ }
+
+ $subpages[++$#subpages] = $u;
+ }
+ }
+
+ if ( $#subpages <= 0 ) {
+ if (!$skipped) {
+ print STDERR "Found nothing on $base\n";
+ }
+ return undef;
+ }
+
+ # pick a random element of the table
+ my $i = ((rand() * 99999) % $#subpages);
+ my $subpage = $subpages[$i];
+
+ if ( $DEBUG > 2 ) {
+ print STDERR "picked page $subpage\n";
+ }
+
+
+
+ my ( $base2, $body2 ) = get_document $subpage;
+
+ return undef if (!$base2 || !body2);
+
+ my $img = pick_image_from_body ($base2, $body2);
+
+ if ($img) {
+ return ($base2, $img);
+ } else {
+ return undef;
+ }
+}
+
+
+# Picks a random image on a random page, and returns two URLs:
+# the page containing the image, and the image.
+# Returns undef if nothing found this time.
+# Uses the url-randomizer 1 time in 5, else the image randomizer.
+#
+sub pick_image {
+ if (int(rand 5) == 0) {
+ return pick_from_url_randomizer;
+ } else {
+ return pick_from_image_randomizer;
+ }
+}
+
+
+# returns the full path of the named program, or undef.
+#
+sub which {
+ my ($prog) = @_;
+ foreach (split (/:/, $ENV{PATH})) {
+ if (-x "$_/$prog") {
+ return $prog;
+ }
+ }
+ return undef;
+}
+
+
+
+#################################
+#
+# running as a CGI
+#
+#################################
+
+
+sub do_html_output {
+
+ $| = 1;
+
+ if ( $progname =~ m/nph-/o ) {
+ print "HTTP/1.0 200 OK\n";
+ print "Content-type: text/html\n";
+ print "\n";
+ }
+
+ print "<TITLE>random images</TITLE>\n";
+ print "<BODY BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\"";
+ print " LINK=\"#0000EE\" VLINK=\"#551A8B\" ALINK=\"#FF0000\">\n";
+ print "<H1 ALIGN=CENTER>random images</H1><P>\n";
+ print "<P><BLOCKQUOTE><BLOCKQUOTE>\n";
+ print "These images have been selected randomly from the web,\n";
+ print "by using both <A HREF=\"$random_redirector\">\n";
+ print "$random_redirector</A> and <A HREF=\"$image_randomizer_a\">\n";
+ print "$image_randomizer_a</A> as a source of URLs from which\n";
+ print "images are extracted.\n";
+ print "<P>\n";
+ print "Note: if you leave this running\n";
+ print "long enough, your browser will undoubtedly run out of memory\n";
+ print "and crash...\n";
+ print "</BLOCKQUOTE></BLOCKQUOTE><P><HR><P ALIGN=CENTER>\n";
+
+ do {
+ my ($base, $img) = pick_image;
+ if ($img) {
+ if ($DEBUG > 0) {
+ print STDERR "$img\n";
+ }
+ print "<A HREF=\"$base\">";
+ print "<IMG ALIGN=MIDDLE BORDER=0 SRC=\"$img\"></A>\n";
+ }
+
+ sleep $delay;
+
+ } while (1);
+}
+
+
+#################################
+#
+# running as an xscreensaver mode
+#
+#################################
+
+
+my $image = ($ENV{TMPDIR} ? $ENV{TMPDIR} : "/tmp") . "/webcollage." . $$;
+my $tmp = $image . "-1";
+my $tmp2 = $image . "-2";
+my $tmp3 = $image . "-3";
+
+sub x_cleanup {
+ if ($DEBUG > 0) { print STDERR "caught signal\n"; }
+ unlink $image, $tmp, $tmp2, $tmp3;
+ exit 1;
+}
+
+my $screen_width = undef;
+my $screen_height = undef;
+
+sub do_x_output {
+
+ my $win_cmd = $ppm_to_root_window_cmd;
+ $win_cmd =~ s/^([^ \t\r\n]+).*$/$1/;
+
+ # make sure the various programs we execute exist, right up front.
+ foreach ("ppmmake", "giftopnm", "djpeg", "pnmpaste", $win_cmd) {
+ which ($_) || die "$progname: $_ not found on \$PATH.\n";
+ }
+
+ $SIG{HUP} = \&x_cleanup;
+ $SIG{INT} = \&x_cleanup;
+ $SIG{QUIT} = \&x_cleanup;
+ $SIG{ABRT} = \&x_cleanup;
+ $SIG{KILL} = \&x_cleanup;
+ $SIG{TERM} = \&x_cleanup;
+
+ # Need this so that if giftopnm dies, we don't die.
+ $SIG{PIPE} = 'IGNORE';
+
+ if (!$screen_width || !$screen_height) {
+ $_ = `xdpyinfo`;
+ ($screen_width, $screen_height) = m/dimensions: *([0-9]+)x([0-9]+) /;
+ }
+
+ my $bgcolor = "#000000";
+ my $bgimage = undef;
+
+ if ($background) {
+ if ($background =~ m/^\#[0-9a-f]+$/i) {
+ $bgcolor = $background;
+ } elsif (-r $background) {
+ $bgimage = $background;
+
+ } elsif (! $background =~ m@^[-a-z0-9 ]+$@i) {
+ print STDERR "not a color or readable file: $background\n";
+ exit 1;
+ } else {
+ # default to assuming it's a color
+ $bgcolor = $background;
+ }
+ }
+
+ # Create the sold-colored base image.
+ #
+ $_ = "ppmmake '$bgcolor' $screen_width $screen_height";
+ if ($DEBUG > 1) {
+ print STDERR "creating base image: $_\n";
+ }
+ system "$_ > $image";
+
+ # Paste the default background image in the middle of it.
+ #
+ if ($bgimage) {
+ my ($iw, $ih);
+ if (open(IMG, "<$bgimage")) {
+ $_ = <IMG>;
+ $_ = <IMG>;
+ ($iw, $ih) = m/^([0-9]+) ([0-9]+)$/;
+ close (IMG);
+ }
+ my $x = int (($screen_width - $iw) / 2);
+ my $y = int (($screen_height - $ih) / 2);
+ if ($DEBUG > 1) {
+ print STDERR "pasting $bgimage into base image at $x, $y\n";
+ }
+ system "pnmpaste $bgimage $x $y $image > $tmp2 && mv $tmp2 $image";
+ }
+
+
+ do {
+ my ($base, $img) = pick_image;
+
+ my ($headers, $body);
+ if ($img) {
+ ($headers, $body) = get_document ($img);
+ }
+
+ if ($body) {
+
+ if ($DEBUG > 0) {
+ print STDERR "got $img (" . length($body) . ")\n";
+ }
+
+ my $cmd;
+ if ($img =~ m/\.gif/i) {
+ $cmd = "giftopnm";
+ } else {
+ $cmd = "djpeg";
+ }
+
+ if ($DEBUG == 0) {
+ $cmd .= " 2>/dev/null";
+ }
+
+ if (open(PIPE, "| $cmd > $tmp")) {
+ print PIPE $body;
+ close PIPE;
+
+ if ($DEBUG > 1) {
+ print STDERR "created $tmp ($cmd)\n";
+ }
+ }
+
+ if (-s $tmp) {
+
+ if ($filter_cmd) {
+ if ($DEBUG > 1) {
+ print STDERR "running $filter_cmd\n";
+ }
+ system "($filter_cmd) < $tmp > $tmp3 && mv $tmp3 $tmp";
+ }
+
+ my ($iw, $ih);
+ if (open(IMG, "<$tmp")) {
+ $_ = <IMG>;
+ $_ = <IMG>;
+ ($iw, $ih) = m/^([0-9]+) ([0-9]+)$/;
+ close (IMG);
+ }
+
+ if ($iw && $ih) {
+
+ if ($DEBUG > 1) {
+ print STDERR "image size is $iw x $ih\n";
+ }
+
+ if ($iw > $screen_width || $ih > $screen_height) {
+ while ($iw > $screen_width ||
+ $ih > $screen_height) {
+ $iw = int($iw / 2);
+ $ih = int($ih / 2);
+ }
+ if ($DEBUG > 1) {
+ print STDERR "scaling to $iw x $ih\n";
+ }
+ system "pnmscale -xysize $iw $ih $tmp > $tmp2" .
+ " 2>/dev/null && mv $tmp2 $tmp";
+ }
+
+ my $x = int (rand() * ($screen_width - $iw));
+ my $y = int (rand() * ($screen_height - $ih));
+
+ if ($DEBUG > 1) {
+ print STDERR "pasting at $x, $y in $image\n";
+ }
+
+ system "pnmpaste $tmp $x $y $image > $tmp2 " .
+ "&& mv $tmp2 $image";
+
+
+ my $target = $image;
+ if ($post_filter_cmd) {
+ if ($DEBUG > 1) {
+ print STDERR "running $post_filter_cmd\n";
+ }
+ system "($post_filter_cmd) < $image > $tmp3";
+ $target = $tmp3;
+ }
+
+ if (!$no_output_p) {
+
+ my $tsize = (stat($target))[7];
+ if ($tsize > 200) {
+ $_ = $ppm_to_root_window_cmd;
+ s/%%PPM%%/$target/;
+
+ if ($DEBUG > 1) {
+ print STDERR "running $_\n";
+ }
+ system $_;
+
+ } elsif ($DEBUG > 1) {
+ print STDERR "$target size is $tsize\n";
+ }
+ }
+ }
+ }
+ unlink $tmp, $tmp2, $tmp3;
+ }
+
+ sleep $delay;
+
+ } while (1);
+}
+
+
+#################################
+#
+# decide how to run
+#
+#################################
+
+sub main {
+ srand(time ^ $$);
+
+ my $usage ="WebCollage, Copyright (c) 1999" .
+ " Jamie Zawinski <jwz\@jwz.org>\n" .
+ " http://www.jwz.org/xscreensaver/\n";
+
+ if ( $progname =~ m/\.cgi$/i ) {
+ $#ARGV == -1 || die "$usage\nusage: $progname (no arguments)\n";
+ do_html_output;
+
+ } else {
+ my $root_p = 0;
+
+ while ($_ = $ARGV[0]) {
+ shift @ARGV;
+ if ($_ eq "-display" ||
+ $_ eq "-displ" ||
+ $_ eq "-disp" ||
+ $_ eq "-dis" ||
+ $_ eq "-dpy" ||
+ $_ eq "-d") {
+ $ENV{DISPLAY} = shift @ARGV;
+ } elsif ($_ eq "-root") {
+ $root_p = 1;
+ } elsif ($_ eq "-no-output") {
+ $no_output_p = 1;
+ } elsif ($_ eq "-verbose") {
+ $DEBUG++;
+ } elsif (m/^-v+$/) {
+ $DEBUG += length($_)-1;
+ } elsif ($_ eq "-delay") {
+ $delay = shift @ARGV;
+ } elsif ($_ eq "-timeout") {
+ $http_timeout = shift @ARGV;
+ } elsif ($_ eq "-filter") {
+ $filter_cmd = shift @ARGV;
+ } elsif ($_ eq "-filter2") {
+ $post_filter_cmd = shift @ARGV;
+ } elsif ($_ eq "-background" || $_ eq "-bg") {
+ $background = shift @ARGV;
+ } elsif ($_ eq "-size") {
+ $_ = shift @ARGV;
+ if (m@^([0-9]+)x([0-9]+)$@) {
+ $screen_width = $1;
+ $screen_height = $2;
+ } else {
+ die "$progname: argument to \"-size\" must be" .
+ " of the form \"640x400\"\n";
+ }
+ } else {
+ die "$usage\nusage: $progname [-root]" .
+ " [-display dpy] [-root] [-verbose] [-timeout secs]\n" .
+ "\t\t [-delay secs] [-filter cmd] [-filter2 cmd]\n";
+ }
+ }
+ if (!$root_p && !$no_output_p) {
+ die "$progname: the -root argument is manditory (for now.)\n";
+ }
+
+ if (!$no_output_p && !$ENV{DISPLAY}) {
+ die "$progname: \$DISPLAY is not set.\n";
+ }
+
+ do_x_output;
+ }
+}
+
+main;
+exit 0;