http://www.jwz.org/xscreensaver/xscreensaver-5.09.tar.gz
[xscreensaver] / hacks / webcollage
index 4ed61c7b434dfc7866d323bb397d3d421f74b131..520e53ae5e9efb423f7a578bc329deb76f62c974 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 #
-# webcollage, Copyright (c) 1999-2005 by Jamie Zawinski <jwz@jwz.org>
+# webcollage, Copyright (c) 1999-2008 by Jamie Zawinski <jwz@jwz.org>
 # This program decorates the screen with random images from the web.
 # One satisfied customer described it as "a nonstop pop culture brainbath."
 #
@@ -60,20 +60,31 @@ use bytes;  # Larry can take Unicode and shove it up his ass sideways.
 
 
 my $progname = $0; $progname =~ s@.*/@@g;
-my $version = q{ $Revision: 1.127 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/;
-my $copyright = "WebCollage $version, Copyright (c) 1999-2005" .
+my $version = q{ $Revision: 1.149 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/;
+my $copyright = "WebCollage $version, Copyright (c) 1999-2008" .
     " Jamie Zawinski <jwz\@jwz.org>\n" .
     "            http://www.jwz.org/webcollage/\n";
 
 
 
-my @search_methods = (  58, "altavista",    \&pick_from_alta_vista_random_link,
-                        11, "livejournal",  \&pick_from_livejournal_images,
-                         7, "yahoorand",    \&pick_from_yahoo_random_link,
-                        10, "googlephotos", \&pick_from_google_image_photos,
-                         6, "googleimgs",   \&pick_from_google_images,
-                         3, "googlenums",   \&pick_from_google_image_numbers,
-                         5, "flickr",       \&pick_from_flickr,
+my @search_methods = ( 20, "googlephotos",  \&pick_from_google_image_photos,
+                       10, "googleimgs",    \&pick_from_google_images,
+                       10, "googlenums",    \&pick_from_google_image_numbers,
+
+                       19, "altavista",     \&pick_from_alta_vista_random_link,
+                       12, "flickr_recent", \&pick_from_flickr_recent,
+                       10, "flickr_random", \&pick_from_flickr_random,
+                       10, "livejournal",   \&pick_from_livejournal_images,
+                        5, "twitter",       \&pick_from_twitter_images,
+                        4, "yahoorand",     \&pick_from_yahoo_random_link,
+
+
+                     # This is a cute way to search for a certain webcams.
+                     # Not included in default methods, since these images
+                     # aren't terribly interesting by themselves.
+                     # See also "SurveillanceSaver".
+                     #
+                        0, "securitycam",   \&pick_from_security_camera,
 
                      # In Apr 2002, Google asked me to stop searching them.
                      # I asked them to add a "random link" url.  They said
@@ -180,6 +191,11 @@ my %poisoners = (
                                    # (I don't see how they did it, though!)
   "alwayshotels.com"        => 1,  # Poisoned Lycos pretty heavily.
   "nextag.com"              => 1,  # Poisoned Alta Vista real good.
+  "ghettodriveby.com"       => 1,  # Poisoned Google Images.
+  "crosswordsolver.org"     => 1,  # Poisoned Google Images.
+  "xona.com"                => 1,  # Poisoned Google Images.
+  "freepatentsonline.com"   => 1,  # Poisoned Google Images.
+  "herbdatanz.com"          => 1,  # Poisoned Google Images.
 );
 
 
@@ -198,6 +214,12 @@ my %warningless_sites = (
   "pics.livejournal.com"    => 1,
   "tinypic.com"             => 1,
   "flickr.com"              => 1,
+  "pbase.com"               => 1,
+  "blogger.com"             => 1,
+  "multiply.com"            => 1,
+  "wikimedia.org"           => 1,
+  "twitpic.com"             => 1,
+  "amazonaws.com"           => 1,  # used by twitpic.com
 
   "yimg.com"                => 1,  # This is where dailynews.yahoo.com stores
   "eimg.com"                => 1,  # its images, so pick_from_yahoo_news_text()
@@ -208,6 +230,7 @@ my %warningless_sites = (
   "images.quizdiva.net"     => 1,
 
   "driftnet"                => 1,  # builtin...
+  "local-directory"         => 1,  # builtin...
 );
 
 
@@ -240,7 +263,19 @@ my %entity_table = (
    "ocirc"  => 'ô', "otilde" => 'õ', "ouml"   => 'ö', "divide" => '÷',
    "oslash" => 'ø', "ugrave" => 'ù', "uacute" => 'ú', "ucirc"  => 'û',
    "uuml"   => 'ü', "yacute" => 'ý', "thorn"  => 'þ', "yuml"   => 'ÿ',
-   "ndash"  => '-', "mdash"  => "--"
+
+   # HTML 4 entities that do not have 1:1 Latin1 mappings.
+   "bull"  => "*",   "hellip"=> "...",  "prime" => "'",  "Prime" => "\"",
+   "frasl" => "/",   "trade" => "[tm]", "larr"  => "<-", "rarr"  => "->",
+   "harr"  => "<->", "lArr"  => "<=",   "rArr"  => "=>", "hArr"  => "<=>",
+   "empty" => "Ø",   "minus" => "-",    "lowast"=> "*",  "sim"   => "~",
+   "cong"  => "=~",  "asymp" => "~",    "ne"    => "!=", "equiv" => "==",
+   "le"    => "<=",  "ge"    => ">=",   "lang"  => "<",  "rang"  => ">",
+   "loz"   => "<>",  "OElig" => "OE",   "oelig" => "oe", "Yuml"  => "Y",
+   "circ"  => "^",   "tilde" => "~",    "ensp"  => " ",  "emsp"  => " ",
+   "thinsp"=> " ",   "ndash" => "-",    "mdash" => "--", "lsquo" => "`",
+   "rsquo" => "'",   "sbquo" => "'",    "ldquo" => "\"", "rdquo" => "\"",
+   "bdquo" => "\"",  "lsaquo"=> "<",    "rsaquo"=> ">",
 );
 
 
@@ -281,6 +316,7 @@ my $min_gif_area = (120 * 120);
 
 my $no_output_p = 0;
 my $urls_only_p = 0;
+my $cocoa_p = 0;
 my $imagemap_base = undef;
 
 my @pids_to_kill = ();  # forked pids we should kill when we exit, if any.
@@ -289,6 +325,9 @@ my $driftnet_magic = 'driftnet';
 my $driftnet_dir = undef;
 my $default_driftnet_cmd = "driftnet -a -m 100";
 
+my $local_magic = 'local-directory';
+my $local_dir = undef;
+
 my $wordlist;
 
 my %rejected_urls;
@@ -390,10 +429,17 @@ sub get_document_1($$$) {
 
       if ($url =~ m@^http://www\.altavista\.com/@ ||
           $url =~ m@^http://random\.yahoo\.com/@ ||
-          $url =~ m@^http://images\.google\.com/@) {
+          $url =~ m@^http://images\.google\.com/@ ||
+          $url =~ m@^http://www\.google\.com/@) {
         # block this, you turkeys.
-        $user_agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.5)" .
-          " Gecko/20041111 Firefox/1.0";
+        $user_agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.7)" .
+          " Gecko/20070914 Firefox/2.0.0.7";
+
+        # 28-Jun-2007: Google Images now emits the entire page in JS if
+        # you claim to be Gecko.  They also still block "webcollage".
+        # They serve non-JS for unrecognised agents, so let's try this...
+        $user_agent = "NoJavascriptPlease/1.0"
+          if ($url =~ m@^http://[a-z]+\.google\.com/@);
       }
 
       my $hdrs = "GET " . ($http_proxy ? $url : "/$path") . " HTTP/1.0\r\n" .
@@ -485,6 +531,10 @@ sub get_document($$;$) {
     return get_driftnet_file ($url);
   }
 
+  if (defined($referer) && $referer eq $local_magic) {
+    return get_local_file ($url);
+  }
+
   my $orig_url = $url;
   my $loop_count = 0;
   my $max_loop_count = 4;
@@ -803,6 +853,29 @@ sub pick_image_from_body($$) {
   return $url;
 }
 
+# Given a URL and the RSS feed from that URL, pick a random image from
+# the feed.  This is a lot simpler than extracting images out of a page:
+# we already know we have reasonable images, so we just pick one.
+# Returns: the real URL of the page (preferably not the RSS version),
+# and the image.
+
+sub pick_image_from_rss($$) {
+  my ( $url, $body ) = @_;
+  my @suitable = ($body =~ m/<enclosure url="(.*?)"/g);
+
+  my ($base) = ($body =~ m@<link>([^<>]+)</link>@i);
+  $base = $url unless $base;
+
+  # pick a random element of the table
+  if (@suitable) {
+    my $i = int(rand(scalar @suitable));
+    my $url = $suitable[$i];
+    LOG ($verbose_load, "picked image " .($i+1) . "/" . 
+                        ($#suitable+1) . ": $url");
+    return ($base, $url);
+  }
+  return;
+}
 
 \f
 ############################################################################
@@ -815,7 +888,10 @@ sub pick_image_from_body($$) {
 sub pick_dictionary() {
   my @dicts = ("/usr/dict/words",
                "/usr/share/dict/words",
-               "/usr/share/lib/dict/words");
+               "/usr/share/lib/dict/words",
+               "/usr/share/dict/cracklib-small",
+               "/usr/share/dict/cracklib-words"
+               );
   foreach my $f (@dicts) {
     if (-f $f) {
       $wordlist = $f;
@@ -1150,6 +1226,7 @@ sub pick_from_yahoo_random_link($) {
 ############################################################################
 #
 # Pick images from random pages returned by the Alta Vista Random Link
+# Note: this seems to have gotten a *lot* less random lately (2007).
 #
 ############################################################################
 
@@ -1248,6 +1325,55 @@ sub pick_from_alta_vista_images($) {
 }
 
 
+\f
+############################################################################
+#
+# Pick images from Aptix security cameras
+# Cribbed liberally from google image search code.
+# By Jason Sullivan <jasonsul@us.ibm.com>
+#
+############################################################################
+
+my $aptix_images_url = ("http://www.google.com/search" .
+                        "?q=inurl:%22jpg/image.jpg%3Fr%3D%22");
+
+# securitycam
+sub pick_from_security_camera($) {
+  my ($timeout) = @_;
+
+  my $page = (int(rand(9)) + 1);
+  my $num = 20;                                        # 20 images per page
+  my $search_url = $aptix_images_url;
+
+  if ($page > 1) {
+    $search_url .= "&start=" . $page*$num;     # page number
+    $search_url .= "&num="   . $num;           #images per page
+  }
+
+  my ($search_hit_count, @subpages) =
+    pick_from_search_engine ($timeout, $search_url, '');
+
+  my @candidates = ();
+  my %referers;
+  foreach my $u (@subpages) {
+    next if ($u =~ m@[/.]google\.com\b@i);        # skip google builtins (most links)
+    next unless ($u =~ m@jpg/image.jpg\?r=@i);    #  All pics contain this
+
+    LOG ($verbose_filter, "  candidate: $u");
+    push @candidates, $u;
+    $referers{$u} = $u;
+    }
+
+  @candidates = depoison (@candidates);
+  return () if ($#candidates < 0);
+  my $i = int(rand($#candidates+1));
+  my $img = $candidates[$i];
+  my $ref = $referers{$img};
+
+  LOG ($verbose_load, "picked image " . ($i+1) . ": $img (on $ref)");
+  return ($ref, $img);
+}
+
 \f
 ############################################################################
 #
@@ -1743,6 +1869,105 @@ sub pick_from_ircimages($) {
   return ($search_url, $img);
 }
 
+\f
+############################################################################
+#
+# Pick images from Twitter's list of recently-posted images.
+#
+############################################################################
+
+my $twitter_img_url = "http://twitpic.com/public_timeline/feed.rss";
+
+# With most of our image sources, we get a random page and then select
+# from the images on it.  However, in the case of Twitter, the page
+# of images tends to update slowly; so we'll remember the last N entries
+# on it and randomly select from those, to get a wider variety each time.
+
+my $twit_cache_size = 1000;
+my @twit_cache = (); # fifo, for ordering by age
+my %twit_cache = (); # hash, for detecting dups
+
+# twitter
+sub pick_from_twitter_images($) {
+  my ($timeout) = @_;
+
+  $last_search = $twitter_img_url;   # for warnings
+
+  my ( $base, $body ) = get_document ($twitter_img_url, undef, $timeout);
+
+  # Update the cache.
+
+  if ($body) {
+    $body =~ s/\n/ /gs;
+    $body =~ s/(<item)\b/\n$1/gsi;
+
+    my @items = split (/\n/, $body);
+    shift @items;
+    foreach (@items) {
+      next unless (m@<link>([^<>]*)</link>@si);
+      my $page = html_unquote ($1);
+
+      $page =~ s@/$@@s;
+      $page .= '/full';
+
+      next if ($twit_cache{$page}); # already have it
+
+      LOG ($verbose_filter, "  candidate: $page");
+      push @twit_cache, $page;
+      $twit_cache{$page} = $page;
+    }
+  }
+
+  # Pull from the cache.
+
+  return () if ($#twit_cache == -1);
+
+  my $n = $#twit_cache+1;
+  my $i = int(rand($n));
+  my $page = $twit_cache[$i];
+
+  # delete this one from @twit_cache and from %twit_cache.
+  #
+  @twit_cache = ( @twit_cache[0 .. $i-1],
+                  @twit_cache[$i+1 .. $#twit_cache] );
+  delete $twit_cache{$page};
+
+  # Keep the size of the cache under the limit by nuking older entries
+  #
+  while ($#twit_cache >= $twit_cache_size) {
+    my $page = shift @twit_cache;
+    delete $twit_cache{$page};
+  }
+
+  ( $base, $body ) = get_document ($page, undef, $timeout);
+  my $img = undef;
+
+  foreach (split (/<img\s+/, $body)) {
+    my ($src) = m/\bsrc=[\"\'](.*?)[\"\']/si;
+    next unless $src;
+    next if m@/js/@s;
+    next if m@/images/@s;
+
+    $img = $src;
+
+    # Sometimes these images are hosted on twitpic, sometimes on Amazon.
+    if ($img =~ m@^/@) {
+      $base =~ s@^(https?://[^/]+)/.*@$1@s;
+      $img = $base . $img;
+    }
+    last;
+  }
+
+  if (!$img) {
+    LOG ($verbose_load, "no matching images on $page\n");
+    return ();
+  }
+
+  LOG ($verbose_load, "picked image " .($i+1) . "/$n: $img");
+
+  return ($page, $img);
+}
+
 \f
 ############################################################################
 #
@@ -1763,8 +1988,8 @@ my @flickr_cache = (); # fifo, for ordering by age
 my %flickr_cache = (); # hash, for detecting dups
 
 
-# flickr
-sub pick_from_flickr($) {
+# flickr_recent
+sub pick_from_flickr_recent($) {
   my ($timeout) = @_;
 
   my $start = 16 * int(rand(100));
@@ -1787,7 +2012,7 @@ sub pick_from_flickr($) {
     $page = html_unquote ($page);
     $thumb = html_unquote ($thumb);
 
-    next unless ($thumb =~ m@^http://photos\d*\.flickr\.com/@);
+    next unless ($thumb =~ m@^http://farm\d*\.static\.flickr\.com/@);
 
     my $base = "http://www.flickr.com/";
     $page  =~ s@^/@$base@;
@@ -1831,6 +2056,47 @@ sub pick_from_flickr($) {
   return ($page, $img);
 }
 
+\f
+############################################################################
+#
+# Pick images from a random RSS feed on Flickr.
+#
+############################################################################
+
+my $flickr_rss_base = ("http://www.flickr.com/services/feeds/photos_public.gne" .
+                       "?format=rss_200_enc&tags=");
+
+# Picks a random RSS feed; picks a random image from that feed;
+# returns 2 URLs: the page containing the image, and the image.
+# Mostly by Joe Mcmahon <mcmahon@yahoo-inc.com>
+#
+# flickr_random
+sub pick_from_flickr_random($) {
+  my $timeout = shift;
+
+  my $rss = $flickr_rss_base . random_word();
+  $last_search = $rss;
+
+  print STDERR "\n\n" if ($verbose_load);
+  LOG ($verbose_load, "URL: $last_search");
+
+  $suppress_audit = 1;
+
+  my ( $base, $body ) = get_document ($last_search, undef, $timeout);
+  if (!$base || !$body) {
+    $body = undef;
+    return;
+  }
+
+  my $img;
+  ($base, $img) = pick_image_from_rss ($base, $body);
+  $body = undef;
+  return () unless defined ($img);
+
+  LOG ($verbose_load, "redirected to: $base");
+  return ($base, $img);
+}
+
 \f
 ############################################################################
 #
@@ -1938,6 +2204,46 @@ sub spawn_driftnet($) {
     unless (1 == kill (0, $pid));
 }
 
+# local-directory
+sub pick_from_local_dir {
+  my ( $timeout ) = @_;
+
+  my $id = $local_magic;
+  $last_search = $id;
+
+  my $dir = $local_dir;
+  error ("\$local_dir unset?") unless ($dir);
+  $dir =~ s@/+$@@;
+
+  error ("$dir unreadable") unless (-d "$dir/.");
+
+  my $v = ($verbose_exec ? "-v" : "");
+  my $pick = `xscreensaver-getimage-file $v "$dir"`;
+
+  LOG ($verbose_load, "picked file $pick ($id)");
+  return ($id, $pick);
+}
+
+
+sub get_local_file {
+  my ($file) = @_;
+
+  error ("\$local_dir unset?") unless ($local_dir);
+
+  my $id = $local_magic;
+  my $re = qr/$local_dir/;
+  error ("$id: $file not in $local_dir?")
+    unless ($file =~ m@^$re@o);
+
+  local *IN;
+  open (IN, $file) || error ("$id: $file: $!");
+  my $body = '';
+  while (<IN>) { $body .= $_; }
+  close IN || error ("$id: $file: $!");
+  return ($id, $body);
+}
+
+
 \f
 ############################################################################
 #
@@ -2163,8 +2469,9 @@ sub report_performance() {
     my $suc = $stats_successes{$name} || 0;
     my $pct = int($suc * 100 / $try);
     my $secs = $stats_elapsed{$name};
-    my $secs_link = int($secs / $try);
-    print STDERR sprintf ("$blurb   %-12s %4s (%d/%d);\t %2d secs/link\n",
+    my $secs_link = $secs / $try;
+    print STDERR sprintf ("$blurb %-14s %4s (%d/%d);" .
+                          "       \t %.1f secs/link\n",
                           "$name:", "$pct%", $suc, $try, $secs_link);
   }
 }
@@ -2185,8 +2492,8 @@ sub save_recent_url($$) {
   my ($site) = m@^http://([^ \t\n\r/:]+)@;
   return unless defined ($site);
 
-  if ($base eq $driftnet_magic) {
-    $site = $driftnet_magic;
+  if ($base eq $driftnet_magic || $base eq $local_magic) {
+    $site = $base;
     @recent_images = ();
   }
 
@@ -2255,6 +2562,7 @@ sub gif_size($) {
   return () unless ($type =~ /GIF8[7,9]a/);
   $s = substr ($body, 6, 10);
   my ($a,$b,$c,$d) = unpack ("C"x4, $s);
+  return () unless defined ($d);
   return (($b<<8|$a), ($d<<8|$c));
 }
 
@@ -2558,9 +2866,43 @@ sub image_to_pnm($$$) {
   }
 }
 
+
+# Same as the "ppmmake" command: creates a solid-colored PPM.
+# Does not understand the rgb.txt color names except "black" and "white".
+#
+sub ppmmake($$$$) {
+  my ($outfile, $bgcolor, $w, $h) = @_;
+
+  my ($r, $g, $b);
+  if ($bgcolor =~ m/^\#?([\dA-F][\dA-F])([\dA-F][\dA-F])([\dA-F][\dA-F])$/i ||
+      $bgcolor =~ m/^\#?([\dA-F])([\dA-F])([\dA-F])$/i) {
+    ($r, $g, $b) = (hex($1), hex($2), hex($3));
+  } elsif ($bgcolor =~ m/^black$/i) {
+    ($r, $g, $b) = (0, 0, 0);
+  } elsif ($bgcolor =~ m/^white$/i) {
+    ($r, $g, $b) = (0xFF, 0xFF, 0xFF);
+  } else {
+    error ("unparsable color name: $bgcolor");
+  }
+
+  my $pixel = pack('CCC', $r, $g, $b);
+  my $bits = "P6\n$w $h\n255\n" . ($pixel x ($w * $h));
+
+  local *OUT;
+  open (OUT, ">$outfile") || error ("$outfile: $!");
+  print OUT $bits;
+  close OUT;
+}
+
+
 sub pick_root_displayer() {
   my @names = ();
 
+  if ($cocoa_p) {
+    # see "xscreensaver/hacks/webcollage-cocoa.m"
+    return "echo COCOA LOAD ";
+  }
+
   foreach my $cmd (@root_displayers) {
     $_ = $cmd;
     my ($name) = m/^([^ ]+)/;
@@ -2594,9 +2936,14 @@ sub x_or_pbm_output($) {
     LOG (($verbose_pbm || $verbose_load), "no $_ program");
   }
 
+  if ($cocoa_p && !defined ($webcollage_helper)) {
+    error ("webcollage-helper not found in Cocoa-mode!");
+  }
+
+
   # make sure the various programs we execute exist, right up front.
   #
-  my @progs = ("ppmmake");  # always need this one
+  my @progs = ();
 
   if (!defined($webcollage_helper)) {
     # Only need these others if we don't have the helper.
@@ -2611,7 +2958,9 @@ sub x_or_pbm_output($) {
 
   # find a root-window displayer program.
   #
-  $ppm_to_root_window_cmd = pick_root_displayer();
+  if (!$no_output_p) {
+    $ppm_to_root_window_cmd = pick_root_displayer();
+  }
 
   if (defined ($window_id)) {
     error ("-window-id only works if xscreensaver-getimage is installed")
@@ -2672,9 +3021,8 @@ sub x_or_pbm_output($) {
 
   # Create the sold-colored base image.
   #
-  $_ = "ppmmake '$bgcolor' $img_width $img_height";
-  LOG ($verbose_pbm, "creating base image: $_");
-  nontrapping_system "$_ > $image_ppm";
+  LOG ($verbose_pbm, "creating base image: ${img_width}x${img_height}");
+  $_ = ppmmake ($image_ppm, $bgcolor, $img_width, $img_height);
 
   # Paste the default background image in the middle of it.
   #
@@ -3031,7 +3379,7 @@ sub paste_image($$$$) {
       # the next network retrieval, which is probably a better thing
       # to do anyway.
       #
-      $cmd .= " &";
+      $cmd .= " &" unless ($cocoa_p);
 
       $rc = nontrapping_system ($cmd);
 
@@ -3164,6 +3512,40 @@ sub update_imagemap($$$$$$$$) {
 }
 
 
+# Figure out what the proxy server should be, either from environment
+# variables or by parsing the output of the (MacOS) program "scutil",
+# which tells us what the system-wide proxy settings are.
+#
+sub set_proxy() {
+
+  if (! $http_proxy) {
+    # historical suckage: the environment variable name is lower case.
+    $http_proxy = $ENV{http_proxy} || $ENV{HTTP_PROXY};
+  }
+
+  if (defined ($http_proxy)) {
+    if ($http_proxy && $http_proxy =~ m@^http://([^/]*)/?$@ ) {
+      # historical suckage: allow "http://host:port" as well as "host:port".
+      $http_proxy = $1;
+    }
+
+  } else {
+    my $proxy_data = `scutil --proxy 2>/dev/null`;
+    my ($server) = ($proxy_data =~ m/\bHTTPProxy\s*:\s*([^\s]+)/s);
+    my ($port)   = ($proxy_data =~ m/\bHTTPPort\s*:\s*([^\s]+)/s);
+    # Note: this ignores the "ExceptionsList".
+    if ($server) {
+      $http_proxy = $server;
+      $http_proxy .= ":$port" if $port;
+    }
+  }
+
+  if ($http_proxy) {
+    LOG ($verbose_net, "proxy server: $http_proxy");
+  }
+}
+
+
 sub init_signals() {
 
   $SIG{HUP}  = \&signal_cleanup;
@@ -3194,9 +3576,6 @@ sub main() {
   my $root_p = 0;
   my $window_id = undef;
 
-  # historical suckage: the environment variable name is lower case.
-  $http_proxy = $ENV{http_proxy} || $ENV{HTTP_PROXY};
-
   while ($_ = $ARGV[0]) {
     shift @ARGV;
     if ($_ eq "-display" ||
@@ -3216,6 +3595,8 @@ sub main() {
     } elsif ($_ eq "-urls-only") {
       $urls_only_p = 1;
       $no_output_p = 1;
+    } elsif ($_ eq "-cocoa") {
+      $cocoa_p = 1;
     } elsif ($_ eq "-imagemap") {
       $imagemap_base = shift @ARGV;
       $no_output_p = 1;
@@ -3256,6 +3637,13 @@ sub main() {
       } else {
         $driftnet_cmd = $default_driftnet_cmd;
       }
+    } elsif ($_ eq "-directory" || $_ eq "--directory") {
+      @search_methods = ( 100, "local", \&pick_from_local_dir );
+      if (! ($ARGV[0] =~ m/^-/)) {
+        $local_dir = shift @ARGV;
+      } else {
+        error ("local directory path must be set")
+      }
     } elsif ($_ eq "-debug" || $_ eq "--debug") {
       my $which = shift @ARGV;
       my @rest = @search_methods;
@@ -3282,25 +3670,18 @@ sub main() {
         "\t\t  [-filter cmd] [-filter2 cmd] [-background color]\n" .
         "\t\t  [-dictionary dictionary-file] [-http-proxy host[:port]]\n" .
         "\t\t  [-driftnet [driftnet-program-and-args]]\n" .
+        "\t\t  [-directory local-image-directory]\n" .
         "\n";
       exit 1;
     }
   }
 
-  if ($http_proxy && $http_proxy eq "") {
-    $http_proxy = undef;
-  }
-  if ($http_proxy && $http_proxy =~ m@^http://([^/]*)/?$@ ) {
-    # historical suckage: allow "http://host:port" as well as "host:port".
-    $http_proxy = $1;
-  }
-
-  if (!$root_p && !$no_output_p) {
+  if (!$root_p && !$no_output_p && !$cocoa_p) {
     print STDERR $copyright;
     error "the -root argument is mandatory (for now.)";
   }
 
-  if (!$no_output_p && !$ENV{DISPLAY}) {
+  if (!$no_output_p && !$cocoa_p && !$ENV{DISPLAY}) {
     error "\$DISPLAY is not set.";
   }
 
@@ -3362,7 +3743,17 @@ sub main() {
     pick_dictionary();
   }
 
+  if ($imagemap_base && !($img_width && $img_height)) {
+    error ("-size WxH is required with -imagemap");
+  }
+
+  if (defined ($local_dir)) {
+    $_ = "xscreensaver-getimage-file";
+    which ($_) || error "$_ not found on \$PATH.";
+  }
+
   init_signals();
+  set_proxy();
 
   spawn_driftnet ($driftnet_cmd) if ($driftnet_cmd);