diff --git a/robots/tracker/RoboTrack.java b/robots/tracker/RoboTrack.java
index 47677b2b4345ec52df7406f83f88647c60922352..b4fb96cebcf15987c3342aab5d93c7609c10ac2c 100644
--- a/robots/tracker/RoboTrack.java
+++ b/robots/tracker/RoboTrack.java
@@ -21,11 +21,13 @@ import java.net.URL;
 import java.net.URLEncoder;
 import java.net.URLConnection;
 import java.text.DecimalFormat;
+import com.sun.image.codec.jpeg.*;
 
 /*
  * Draw the floormap and put little dots on it. 
  */
 public class RoboTrack extends JApplet {
+    int myHeight, myWidth;
     Map map;
     JTable maptable;
     JPopupMenu LeftMenuPopup, RightMenuPopup;
@@ -40,6 +42,8 @@ public class RoboTrack extends JApplet {
     static final Date now = new Date();
     String uid, auth, building, floor;
     boolean shelled = false;
+    WebCam WebCamWorkers[] = null;
+    int WebCamCount = 0;
     
     /*
      * The connection to boss that will provide robot location info.
@@ -50,7 +54,13 @@ public class RoboTrack extends JApplet {
     public void init() {
 	try
 	{
-	    URL urlServer = this.getCodeBase(), robopipe, floorurl;
+	    if (! shelled) {
+		Dimension appletSize = this.getSize();
+		
+		myHeight = appletSize.height;
+		myWidth  = appletSize.width;
+	    }
+	    URL urlServer = this.getCodeBase(), robopipe, floorurl, camurl;
 	    String pipeurl, baseurl;
 	    URLConnection uc;
 
@@ -63,6 +73,40 @@ public class RoboTrack extends JApplet {
 	    floor    = this.getParameter("floor");
 	    pixels_per_meter = Double.parseDouble(this.getParameter("ppm"));
 
+	    if (this.getParameter("WebCamCount") != null) {
+		int count =
+		    Integer.parseInt(this.getParameter("WebCamCount").trim());
+		WebCamWorkers = new WebCam[count];
+		System.out.println("There are " + count + " webcams");
+		
+		for (int i = 0; i < count; i++) {
+		    String camname   = "WebCam" + i;
+		    String coordname = "WebCam" + i + "XY";
+		    int x, y;
+
+		    if (this.getParameter(camname) == null ||
+			this.getParameter(coordname) == null)
+			continue;
+
+		    camurl = new URL(urlServer,
+				     this.getParameter(camname)
+				     + "&nocookieuid="
+				     + URLEncoder.encode(uid)
+				     + "&nocookieauth="
+				     + URLEncoder.encode(auth));
+
+		    StringTokenizer tokens =
+			new StringTokenizer(this.getParameter(coordname), ",");
+
+		    x = Integer.parseInt(tokens.nextToken().trim());
+		    y = Integer.parseInt(tokens.nextToken().trim());
+
+		    WebCamWorkers[WebCamCount] =
+			new WebCam(WebCamCount, camurl, x, y);
+		    WebCamCount++;
+		}
+	    }
+
 	    // form the URL that we use to get the background image
 	    floorurl = new URL(urlServer,
 			       baseurl
@@ -105,24 +149,61 @@ public class RoboTrack extends JApplet {
 	    });	
 	 */
 
-	// This is used below for the listeners. 
-	MyMouseAdaptor mymouser = new MyMouseAdaptor();
-	
 	/*
-	 * Vertical placement of Components in the pane. 
+	 * Going to use the layered pane so that we can put the cameras
+	 * in front of the map. But first I need to create a JPanel to
+	 * contain the map and table. Why? Cause a layeredPane has no
+	 * real layout management, and I do not want to use absolute
+	 * positioning for that. So, put everything inside a JPanel, and
+	 * then put the JPanel inside the root layeredPane. The put the
+	 * webcams on top of that.
 	 */
-	getContentPane().setLayout(new BoxLayout(getContentPane(),
-						 BoxLayout.PAGE_AXIS));
-        getContentPane().add(map = new Map());
+	JLayeredPane MyLPane  = getLayeredPane();
+	JPanel       MyJPanel = new JPanel(true);
+
+	/*
+	 * Specify vertical placement of components inside my JPanel.
+	 */
+	MyJPanel.setLayout(new BoxLayout(MyJPanel, BoxLayout.Y_AXIS));
+
+	/*
+	 * Now add the basic objects to the JPanel.
+	 */
+	map = new Map();
+        MyJPanel.add(map);
 
 	/*
 	 * This sillyness is how you get the column headers to show
 	 * when you do not put the Jtable inside a scroll thingy.
 	 */
 	maptable = new JTable(new MyTableModel());
-	getContentPane().add(maptable.getTableHeader(), BorderLayout.NORTH);
-	getContentPane().add(maptable, BorderLayout.CENTER);
+	MyJPanel.add(maptable.getTableHeader());
+	MyJPanel.add(maptable);
 
+	/*
+	 * Add the JPanel to the layeredPane, but give its size since
+	 * there is no layout manager.
+	 */
+	MyLPane.add(MyJPanel, JLayeredPane.DEFAULT_LAYER);
+	MyJPanel.setBounds(0, 0, myWidth, myHeight);
+
+	/*
+	 * Now, if we have webcams, put them on the layered pane, on top
+	 * of the the JPanel that holds the other stuff.
+	 */
+	if (WebCamCount > 0) {
+	    for (int i = 0; i < WebCamCount; i++) {
+		WebCam cam = WebCamWorkers[i];
+
+		MyLPane.add(cam, JLayeredPane.PALETTE_LAYER);
+		
+		cam.setBounds(cam.X, cam.Y, cam.W, cam.H);
+	    }
+	}
+
+	// This is used below for the listeners. 
+	MyMouseAdaptor mymouser = new MyMouseAdaptor();
+	
 	/*
 	 * Create the popup menu that will be used to fire off
 	 * robot moves. We use the mouseadaptor for this too. 
@@ -172,9 +253,16 @@ public class RoboTrack extends JApplet {
 
     public void start() {
         map.start();
+
+	for (int i = 0; i < WebCamCount; i++) {
+	    WebCamWorkers[i].start();
+	}
     }
   
     public void stop() {
+	for (int i = 0; i < WebCamCount; i++) {
+	    WebCamWorkers[i].stop();
+	}
         map.stop();
     }
 
@@ -193,7 +281,11 @@ public class RoboTrack extends JApplet {
 	    MediaTracker tracker = new MediaTracker(this);
 	    tracker.addImage(img, 0);
 	    tracker.waitForID(0);
-	} catch (Exception e) {}
+	}
+	catch(Throwable th)
+	{
+	    th.printStackTrace();
+	}
 	return img;
     }
 
@@ -677,7 +769,7 @@ public class RoboTrack extends JApplet {
 	    }
 	    
 	    /*
-	     * If there is a orientation, add an orientation stick
+	     * If there is an orientation, add an orientation stick
 	     * to it.
 	     */
 	    if (or != 500.0) {
@@ -788,12 +880,19 @@ public class RoboTrack extends JApplet {
 	    int w = dim.width;
 
 	    //System.out.println(w + " D " + h);
-	    //System.out.println(rect.width + " R " + rect.height);
-	    
-            Graphics2D g2 = createGraphics2D(w, h);
 
-            drawMap(w, h, g2);
-            g.drawImage(bimg, 0, 0, this);
+	    if (true) {
+		Graphics2D g2 = (Graphics2D)g;
+		
+		g2.clearRect(0, 0, w, h);
+		drawMap(w, h, g2);
+	    }
+	    else {
+		Graphics2D g2 = createGraphics2D(w, h);
+		
+		drawMap(w, h, g2);
+		g.drawImage(bimg, 0, 0, this);
+	    }
         }
     
         public void start() {
@@ -1702,14 +1801,210 @@ public class RoboTrack extends JApplet {
 				      JOptionPane.ERROR_MESSAGE);
     }
 
+    public class WebCam extends JPanel implements Runnable {
+	private BufferedImage	curimage = null;
+	private URL		myurl;
+        private Thread		camthread;
+	private int		myid = 0;
+	private InputStream	is = null;
+	public  int		X, Y, W, H;
+	
+        public WebCam(int id, URL webcamurl, int x, int y) {
+	    myid  = id;
+	    myurl = webcamurl;
+	    X     = x;
+	    Y     = y;
+	    W     = 240;
+	    H     = 180;
+
+	    System.out.println("Creating a WebCam class: " +
+			       id + "," + X + "," + Y);
+
+	    super.setDoubleBuffered(true);
+        }
+
+        public void start() {
+            camthread = new Thread(this);
+            camthread.start();
+        }
+    
+        public synchronized void stop() {
+            camthread = null;
+        }
+    
+	/*
+	 * This is the callback to paint the map. 
+	 */
+        public void paint(Graphics g) {
+            Dimension dim  = getSize();
+	    int h = dim.height;
+	    int w = dim.width;
+
+	    if (curimage == null)
+		return;
+	    
+	    g.drawImage(curimage, 0, 0, this);
+        }
+
+        public void run() {
+            Thread me = Thread.currentThread();
+	    String lenhdr = "content-length: ";
+	    byte imageBytes[] = new byte[100000];
+	    ByteArrayInputStream imageinput;
+	    String str;
+	    int size;
+
+	    try
+	    {
+		// And connect to it.
+		URLConnection uc = myurl.openConnection();
+		uc.setDoInput(true);
+		uc.setUseCaches(false);
+		this.is = uc.getInputStream();
+	    }
+	    catch(Throwable th)
+	    {
+	        th.printStackTrace();
+		camthread = null;
+		return;
+	    }
+	    BufferedInputStream input = new BufferedInputStream(this.is);
+
+            while (camthread == me) {
+		try
+		{
+		    // First line is the boundry marker
+		    if ((str = readLine(input).trim()) == null)
+			break;
+
+		    if (!str.equals("--myboundary")) {
+			System.out.println("Sync error at boundry");
+			break;
+		    }
+
+		    // Next line is the Content-type. Skip it.
+		    if ((str = readLine(input)) == null)
+			break;
+
+		    // Next line is the Content-length. We need this. 
+		    if ((str = readLine(input)) == null)
+			break;
+		    str = str.toLowerCase();
+
+		    if (! str.startsWith(lenhdr)) {
+			System.out.println("Sync error at content length");
+			break;
+		    }
+		    str  = str.substring(lenhdr.length(), str.length());
+		    size = Integer.parseInt(str.trim());
+		    
+		    if (size < 0) {
+			System.out.println("Bad content length: " + size);
+			break;
+		    }
+		    //System.out.println("content length: " + size);
+		    
+		    // Next line is a blank line. Skip it.
+		    if ((str = readLine(input)) == null)
+			break;
+
+		    // Now we have the data. Buffer that up.
+		    int count = 0;
+		    while (count < size) {
+			int cc = input.read(imageBytes, count, size - count);
+			if (cc == -1)
+			    break;
+
+			count += cc;
+		    }
+		    if (count != size) {
+			System.out.println("Not enough image data");
+			break;
+		    }
+		    // For the jpeg decoder.
+		    imageinput = new ByteArrayInputStream(imageBytes);
+		    
+		    JPEGImageDecoder decoder =
+			JPEGCodec.createJPEGDecoder(imageinput);
+		    
+		    curimage = decoder.decodeAsBufferedImage();
+
+		    //System.out.println("Got the image");
+		    
+		    // Next line is a blank line. Skip it.
+		    if ((str = readLine(input)) == null)
+			break;
+		    
+		    repaint();
+		    me.yield();
+		}
+		catch(IOException e)
+		{
+		    e.printStackTrace();
+		    break;
+		}
+            }
+            camthread = null;
+	    destroy();
+        }
+
+	/*
+	 * Catch termination.
+	 */
+	public void destroy() {
+	    try
+	    {
+	        if (is != null) {
+		    is.close();
+		    is = null;
+		}
+	    }
+	    catch(IOException e)
+	    {
+		e.printStackTrace();
+	    }
+	}
+
+	/*
+	 * A utility function since a BufferedInputStream has no readline().
+	 */
+	private byte[] buf = new byte[1024];
+	
+	private String readLine(BufferedInputStream input) {
+	    int		index = 0;
+	    int		ch;
+
+	    try
+	    {
+		while ((ch = input.read()) != -1) {
+		    buf[index++] = (byte) ch;
+		    if (ch == '\n')
+			break;
+		}
+	    }
+	    catch(IOException e)
+	    {
+		e.printStackTrace();
+	    }
+	    if (index == 0)
+		return "";
+	    return new String(buf, 0, index);
+	}
+    }
+    
     public static void main(String argv[]) {
+	int W = 900;
+	int H = 600;
+	
         final RoboTrack robomap = new RoboTrack();
 	try
 	{
-	    URL url = new URL("file://robots-4.jpg");
+	    URL url = new URL("file:///tmp/robots-4.jpg");
+	    robomap.floorimage = robomap.getImage(url);
+	    robomap.myWidth = W;
+	    robomap.myHeight = H;
 	    robomap.init(true);
 	    robomap.is = System.in;
-	    robomap.floorimage = robomap.getImage(url);
 	    robomap.uid = "stoller";
 	    robomap.auth = "xyz";
 	}
@@ -1727,7 +2022,7 @@ public class RoboTrack extends JApplet {
         });
         f.add(robomap);
         f.pack();
-        f.setSize(new Dimension(900,600));
+        f.setSize(new Dimension(W,H));
         f.show();
         robomap.start();
     }
diff --git a/www/robotmap.php3 b/www/robotmap.php3
index 19f6ba1475d6538eb1fa2b58cf3cec7c38d12e31..400388eb1b6647ffc97c15698c6d191de256367c 100755
--- a/www/robotmap.php3
+++ b/www/robotmap.php3
@@ -263,6 +263,8 @@ if ($isadmin || TBWebCamAllowed($uid)) {
 }
 echo "  <a href=robotrack/robotrack.php3?building=${building}&floor=${floor}>".
         "Track robots in real time</a>";
+echo "  <a href=robotrack/robotrack.php3?building=${building}&floor=${floor}&withwebcams=1>".
+        " (with webcams)</a>";
 echo "  <br>\n";
 
 if (isset($map_x) && isset($map_y)) {
diff --git a/www/robotrack/robopipe.php3 b/www/robotrack/robopipe.php3
index cdebc36265f444063b54f5968c7da65c3a2fe31f..6ac813df0277157575377cbbca14e06dab982574 100644
--- a/www/robotrack/robopipe.php3
+++ b/www/robotrack/robopipe.php3
@@ -133,6 +133,10 @@ while (1) {
 	    $dy  = "";
 	    $dor = "";
 	}
+	else {
+	    $dx = (int) $dx;
+	    $dy = (int) $dy;
+	}
 	if (!isset($bvolts))
 	    $bvolts = "";
 	if (!isset($bper))
diff --git a/www/robotrack/robotrack.php3 b/www/robotrack/robotrack.php3
index 1d0c993fae6553b69801df7e90fee5a2cf24831a..4520b739fb72ed6c5d4b1dffb854e477bc0c7ce6 100644
--- a/www/robotrack/robotrack.php3
+++ b/www/robotrack/robotrack.php3
@@ -45,6 +45,21 @@ else {
     USERERROR("No such building/floor $building/$floor", 1);
 }
 
+#
+# If adding in the webcams, get that stuff too.
+#
+$webcams = array();
+
+if ($withwebcams) {
+  $query_result = DBQueryFatal("select * from webcams");
+
+  while ($row = mysql_fetch_array($query_result)) {
+      $id      = $row["id"];
+      $camurl  = "../webcamimg.php3?webcamid=${id}&applet=1&fromtracker=1";
+      $webcams[] = $camurl;
+  }
+}
+
 #
 # Draw the legend and some explanatory text.
 #
@@ -121,8 +136,29 @@ echo "<applet name='tracker' code='RoboTrack.class'
             <param name='auth' value='$auth'>
             <param name='ppm' value='$ppm'>
             <param name='building' value='$building'>
-            <param name='floor' value='$floor'>
-          </applet>\n";
+            <param name='floor' value='$floor'>";
+if (count($webcams)) {
+    $camcount = count($webcams);
+    $x = 400;
+    $y = 460;
+    
+    echo "<param name='WebCamCount' value='$camcount'>";
+
+    for ($i = 0; $i < $camcount; $i++) {
+	$camurl = $webcams[$i];
+
+	echo "<param name='WebCam${i}' value='$camurl'>
+              <param name='WebCam${i}XY' value='$x,$y'>";
+
+	$x += 260;
+
+	if ($x > 700) {
+	    $x = 400;
+	    $y = $y + 200;
+	}
+    }
+}
+echo "</applet>\n";
 
 echo "<br>
      <blockquote><blockquote>
diff --git a/www/robotrack/tracker.jar b/www/robotrack/tracker.jar
index d52bf225978c9668794e3dd8cc104e99ed413b5a..6929f89be4b9ae85fd83595a6848a2277337abb9 100644
Binary files a/www/robotrack/tracker.jar and b/www/robotrack/tracker.jar differ
diff --git a/www/webcamimg.php3 b/www/webcamimg.php3
index ed19c8dc167bd57dc06ac753de22cfddf21f4e8a..7480883a740724970b44fa5be3d99206fb05fefa 100644
--- a/www/webcamimg.php3
+++ b/www/webcamimg.php3
@@ -51,6 +51,9 @@ if (!$query_result || !mysql_num_rows($query_result)) {
 }
 $row = mysql_fetch_array($query_result);
 $URL = (isset($applet) ? $row["URL"] : $row["stillimage_URL"]);
+if (isset($fromtracker)) {
+    $URL .= "&resolution=240x180&fps=2";
+}
 
 #
 # Check sitevar to make sure mere users are allowed to peek at us.