1/* -*- Mode: Java; tab-width: 4 -*- 2 * 3 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. 4 * 5 * Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. 6 * ("Apple") in consideration of your agreement to the following terms, and your 7 * use, installation, modification or redistribution of this Apple software 8 * constitutes acceptance of these terms. If you do not agree with these terms, 9 * please do not use, install, modify or redistribute this Apple software. 10 * 11 * In consideration of your agreement to abide by the following terms, and subject 12 * to these terms, Apple grants you a personal, non-exclusive license, under Apple's 13 * copyrights in this original Apple software (the "Apple Software"), to use, 14 * reproduce, modify and redistribute the Apple Software, with or without 15 * modifications, in source and/or binary forms; provided that if you redistribute 16 * the Apple Software in its entirety and without modifications, you must retain 17 * this notice and the following text and disclaimers in all such redistributions of 18 * the Apple Software. Neither the name, trademarks, service marks or logos of 19 * Apple Computer, Inc. may be used to endorse or promote products derived from the 20 * Apple Software without specific prior written permission from Apple. Except as 21 * expressly stated in this notice, no other rights or licenses, express or implied, 22 * are granted by Apple herein, including but not limited to any patent rights that 23 * may be infringed by your derivative works or by other works in which the Apple 24 * Software may be incorporated. 25 * 26 * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 27 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 28 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 30 * COMBINATION WITH YOUR PRODUCTS. 31 * 32 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 34 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 35 * ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION 36 * OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT 37 * (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN 38 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 40 SimpleChat is a simple peer-to-peer chat program that demonstrates 41 DNS-SD registration, browsing, resolving and record-querying. 42 43 To do: 44 - implement better coloring algorithm 45 */ 46 47 48import java.awt.*; 49import java.awt.event.*; 50import java.text.*; 51import java.net.*; 52import javax.swing.*; 53import javax.swing.event.*; 54import javax.swing.text.*; 55 56import com.apple.dnssd.*; 57 58 59class SimpleChat implements ResolveListener, RegisterListener, QueryListener, 60 ActionListener, ItemListener, Runnable 61{ 62 Document textDoc; // Holds all the chat text 63 JTextField inputField; // Holds a pending chat response 64 String ourName; // name used to identify this user in chat 65 DNSSDService browser; // object that actively browses for other chat clients 66 DNSSDService resolver; // object that resolves other chat clients 67 DNSSDRegistration registration; // object that maintains our connection advertisement 68 JComboBox targetPicker; // Indicates who we're talking to 69 TargetListModel targetList; // and its list model 70 JButton sendButton; // Will send text in inputField to target 71 InetAddress buddyAddr; // and address 72 int buddyPort; // and port 73 DatagramPacket dataPacket; // Inbound data packet 74 DatagramSocket outSocket; // Outbound data socket 75 SimpleAttributeSet textAttribs; 76 77 static final String kChatExampleRegType = "_p2pchat._udp"; 78 static final String kWireCharSet = "ISO-8859-1"; 79 80 public SimpleChat() throws Exception 81 { 82 JFrame frame = new JFrame("SimpleChat"); 83 frame.addWindowListener(new WindowAdapter() { 84 public void windowClosing(WindowEvent e) {System.exit(0);} 85 }); 86 87 ourName = System.getProperty( "user.name"); 88 targetList = new TargetListModel(); 89 textAttribs = new SimpleAttributeSet(); 90 DatagramSocket inSocket = new DatagramSocket(); 91 dataPacket = new DatagramPacket( new byte[ 4096], 4096); 92 outSocket = new DatagramSocket(); 93 94 this.setupSubPanes( frame.getContentPane(), frame.getRootPane()); 95 frame.pack(); 96 frame.setVisible(true); 97 inputField.requestFocusInWindow(); 98 99 browser = DNSSD.browse( 0, 0, kChatExampleRegType, "", new SwingBrowseListener( targetList)); 100 101 registration = DNSSD.register( 0, 0, ourName, kChatExampleRegType, "", "", inSocket.getLocalPort(), null, this); 102 103 new ListenerThread( this, inSocket, dataPacket).start(); 104 } 105 106 protected void setupSubPanes( Container parent, JRootPane rootPane) 107 { 108 parent.setLayout( new BoxLayout( parent, BoxLayout.Y_AXIS)); 109 110 JPanel textRow = new JPanel(); 111 textRow.setLayout( new BoxLayout( textRow, BoxLayout.X_AXIS)); 112 textRow.add( Box.createRigidArea( new Dimension( 16, 0))); 113 JEditorPane textPane = new JEditorPane( "text/html", "<BR>"); 114 textPane.setPreferredSize( new Dimension( 400, 300)); 115 textPane.setEditable( false); 116 JScrollPane textScroller = new JScrollPane( textPane); 117 textRow.add( textScroller); 118 textRow.add( Box.createRigidArea( new Dimension( 16, 0))); 119 textDoc = textPane.getDocument(); 120 121 JPanel addressRow = new JPanel(); 122 addressRow.setLayout( new BoxLayout( addressRow, BoxLayout.X_AXIS)); 123 targetPicker = new JComboBox( targetList); 124 targetPicker.addItemListener( this); 125 addressRow.add( Box.createRigidArea( new Dimension( 16, 0))); 126 addressRow.add( new JLabel( "Talk to: ")); 127 addressRow.add( targetPicker); 128 addressRow.add( Box.createHorizontalGlue()); 129 130 JPanel buttonRow = new JPanel(); 131 buttonRow.setLayout( new BoxLayout( buttonRow, BoxLayout.X_AXIS)); 132 buttonRow.add( Box.createRigidArea( new Dimension( 16, 0))); 133 inputField = new JTextField(); 134 // prevent inputField from hijacking <Enter> key 135 inputField.getKeymap().removeKeyStrokeBinding( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0)); 136 buttonRow.add( inputField); 137 sendButton = new JButton( "Send"); 138 buttonRow.add( Box.createRigidArea( new Dimension( 8, 0))); 139 buttonRow.add( sendButton); 140 buttonRow.add( Box.createRigidArea( new Dimension( 16, 0))); 141 rootPane.setDefaultButton( sendButton); 142 sendButton.addActionListener( this); 143 sendButton.setEnabled( false); 144 145 parent.add( Box.createRigidArea( new Dimension( 0, 16))); 146 parent.add( textRow); 147 parent.add( Box.createRigidArea( new Dimension( 0, 8))); 148 parent.add( addressRow); 149 parent.add( Box.createRigidArea( new Dimension( 0, 8))); 150 parent.add( buttonRow); 151 parent.add( Box.createRigidArea( new Dimension( 0, 16))); 152 } 153 154 public void serviceRegistered( DNSSDRegistration registration, int flags, 155 String serviceName, String regType, String domain) 156 { 157 ourName = serviceName; // might have been renamed on collision 158 } 159 160 public void operationFailed( DNSSDService service, int errorCode) 161 { 162 System.out.println( "Service reported error " + String.valueOf( errorCode)); 163 } 164 165 public void serviceResolved( DNSSDService resolver, int flags, int ifIndex, String fullName, 166 String hostName, int port, TXTRecord txtRecord) 167 { 168 buddyPort = port; 169 try { 170 // Start a record query to obtain IP address from hostname 171 DNSSD.queryRecord( 0, ifIndex, hostName, 1 /* ns_t_a */, 1 /* ns_c_in */, 172 new SwingQueryListener( this)); 173 } 174 catch ( Exception e) { terminateWithException( e); } 175 resolver.stop(); 176 } 177 178 public void queryAnswered( DNSSDService query, int flags, int ifIndex, String fullName, 179 int rrtype, int rrclass, byte[] rdata, int ttl) 180 { 181 try { 182 buddyAddr = InetAddress.getByAddress( rdata); 183 } 184 catch ( Exception e) { terminateWithException( e); } 185 sendButton.setEnabled( true); 186 } 187 188 public void actionPerformed( ActionEvent e) // invoked when Send button is hit 189 { 190 try 191 { 192 String sendString = ourName + ": " + inputField.getText(); 193 byte[] sendData = sendString.getBytes( kWireCharSet); 194 outSocket.send( new DatagramPacket( sendData, sendData.length, buddyAddr, buddyPort)); 195 StyleConstants.setForeground( textAttribs, Color.black); 196 textDoc.insertString( textDoc.getLength(), inputField.getText() + "\n", textAttribs); 197 inputField.setText( ""); 198 } 199 catch ( Exception exception) { terminateWithException( exception); } 200 } 201 202 public void itemStateChanged( ItemEvent e) // invoked when Target selection changes 203 { 204 sendButton.setEnabled( false); 205 if ( e.getStateChange() == ItemEvent.SELECTED) 206 { 207 try { 208 TargetListElem sel = (TargetListElem) targetList.getSelectedItem(); 209 resolver = DNSSD.resolve( 0, sel.fInt, sel.fServiceName, sel.fType, sel.fDomain, this); 210 } 211 catch ( Exception exception) { terminateWithException( exception); } 212 } 213 } 214 215 public void run() // invoked on event thread when inbound packet arrives 216 { 217 try 218 { 219 String inMessage = new String( dataPacket.getData(), 0, dataPacket.getLength(), kWireCharSet); 220 StyleConstants.setForeground( textAttribs, this.getColorFor( dataPacket.getData(), dataPacket.getLength())); 221 textDoc.insertString( textDoc.getLength(), inMessage + "\n", textAttribs); 222 } 223 catch ( Exception e) { terminateWithException( e); } 224 } 225 226 protected Color getColorFor( byte[] chars, int length) 227 // Produce a mapping from a string to a color, suitable for text display 228 { 229 int rgb = 0; 230 for ( int i=0; i < length && chars[i] != ':'; i++) 231 rgb = rgb ^ ( (int) chars[i] << (i%3+2) * 8); 232 return new Color( rgb & 0x007F7FFF); // mask off high bits so it is a dark color 233 234// for ( int i=0; i < length && chars[i] != ':'; i++) 235 236 } 237 238 protected static void terminateWithException( Exception e) 239 { 240 e.printStackTrace(); 241 System.exit( -1); 242 } 243 244 public static void main(String s[]) 245 { 246 try { 247 new SimpleChat(); 248 } 249 catch ( Exception e) { terminateWithException( e); } 250 } 251} 252 253 254 255class TargetListElem 256{ 257 public TargetListElem( String serviceName, String domain, String type, int ifIndex) 258 { fServiceName = serviceName; fDomain = domain; fType = type; fInt = ifIndex; } 259 260 public String toString() { return fServiceName; } 261 262 public String fServiceName, fDomain, fType; 263 public int fInt; 264} 265 266class TargetListModel extends DefaultComboBoxModel implements BrowseListener 267{ 268 /* The Browser invokes this callback when a service is discovered. */ 269 public void serviceFound( DNSSDService browser, int flags, int ifIndex, 270 String serviceName, String regType, String domain) 271 { 272 TargetListElem match = this.findMatching( serviceName); // probably doesn't handle near-duplicates well. 273 274 if ( match == null) 275 this.addElement( new TargetListElem( serviceName, domain, regType, ifIndex)); 276 } 277 278 /* The Browser invokes this callback when a service disappears. */ 279 public void serviceLost( DNSSDService browser, int flags, int ifIndex, 280 String serviceName, String regType, String domain) 281 { 282 TargetListElem match = this.findMatching( serviceName); // probably doesn't handle near-duplicates well. 283 284 if ( match != null) 285 this.removeElement( match); 286 } 287 288 /* The Browser invokes this callback when a service disappears. */ 289 public void operationFailed( DNSSDService service, int errorCode) 290 { 291 System.out.println( "Service reported error " + String.valueOf( errorCode)); 292 } 293 294 protected TargetListElem findMatching( String match) 295 { 296 for ( int i = 0; i < this.getSize(); i++) 297 if ( match.equals( this.getElementAt( i).toString())) 298 return (TargetListElem) this.getElementAt( i); 299 return null; 300 } 301 302} 303 304 305// A ListenerThread runs its owner when datagram packet p appears on socket s. 306class ListenerThread extends Thread 307{ 308 public ListenerThread( Runnable owner, DatagramSocket s, DatagramPacket p) 309 { fOwner = owner; fSocket = s; fPacket = p; } 310 311 public void run() 312 { 313 while ( true ) 314 { 315 try 316 { 317 fSocket.receive( fPacket); 318 SwingUtilities.invokeAndWait( fOwner); // process data on main thread 319 } 320 catch( Exception e) 321 { 322 break; // terminate thread 323 } 324 } 325 } 326 327 protected Runnable fOwner; 328 protected DatagramSocket fSocket; 329 protected DatagramPacket fPacket; 330} 331 332 333 334