/* -*- Mode: Java; tab-width: 4 -*- * * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. * * Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. * ("Apple") in consideration of your agreement to the following terms, and your * use, installation, modification or redistribution of this Apple software * constitutes acceptance of these terms. If you do not agree with these terms, * please do not use, install, modify or redistribute this Apple software. * * In consideration of your agreement to abide by the following terms, and subject * to these terms, Apple grants you a personal, non-exclusive license, under Apple's * copyrights in this original Apple software (the "Apple Software"), to use, * reproduce, modify and redistribute the Apple Software, with or without * modifications, in source and/or binary forms; provided that if you redistribute * the Apple Software in its entirety and without modifications, you must retain * this notice and the following text and disclaimers in all such redistributions of * the Apple Software. Neither the name, trademarks, service marks or logos of * Apple Computer, Inc. may be used to endorse or promote products derived from the * Apple Software without specific prior written permission from Apple. Except as * expressly stated in this notice, no other rights or licenses, express or implied, * are granted by Apple herein, including but not limited to any patent rights that * may be infringed by your derivative works or by other works in which the Apple * Software may be incorporated. * * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN * COMBINATION WITH YOUR PRODUCTS. * * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION * OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT * (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SimpleChat is a simple peer-to-peer chat program that demonstrates DNS-SD registration, browsing, resolving and record-querying. To do: - implement better coloring algorithm */ import java.awt.*; import java.awt.event.*; import java.text.*; import java.net.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import com.apple.dnssd.*; class SimpleChat implements ResolveListener, RegisterListener, QueryListener, ActionListener, ItemListener, Runnable { Document textDoc; // Holds all the chat text JTextField inputField; // Holds a pending chat response String ourName; // name used to identify this user in chat DNSSDService browser; // object that actively browses for other chat clients DNSSDService resolver; // object that resolves other chat clients DNSSDRegistration registration; // object that maintains our connection advertisement JComboBox targetPicker; // Indicates who we're talking to TargetListModel targetList; // and its list model JButton sendButton; // Will send text in inputField to target InetAddress buddyAddr; // and address int buddyPort; // and port DatagramPacket dataPacket; // Inbound data packet DatagramSocket outSocket; // Outbound data socket SimpleAttributeSet textAttribs; static final String kChatExampleRegType = "_p2pchat._udp"; static final String kWireCharSet = "ISO-8859-1"; public SimpleChat() throws Exception { JFrame frame = new JFrame("SimpleChat"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); ourName = System.getProperty( "user.name"); targetList = new TargetListModel(); textAttribs = new SimpleAttributeSet(); DatagramSocket inSocket = new DatagramSocket(); dataPacket = new DatagramPacket( new byte[ 4096], 4096); outSocket = new DatagramSocket(); this.setupSubPanes( frame.getContentPane(), frame.getRootPane()); frame.pack(); frame.setVisible(true); inputField.requestFocusInWindow(); browser = DNSSD.browse( 0, 0, kChatExampleRegType, "", new SwingBrowseListener( targetList)); registration = DNSSD.register( 0, 0, ourName, kChatExampleRegType, "", "", inSocket.getLocalPort(), null, this); new ListenerThread( this, inSocket, dataPacket).start(); } protected void setupSubPanes( Container parent, JRootPane rootPane) { parent.setLayout( new BoxLayout( parent, BoxLayout.Y_AXIS)); JPanel textRow = new JPanel(); textRow.setLayout( new BoxLayout( textRow, BoxLayout.X_AXIS)); textRow.add( Box.createRigidArea( new Dimension( 16, 0))); JEditorPane textPane = new JEditorPane( "text/html", "
"); textPane.setPreferredSize( new Dimension( 400, 300)); textPane.setEditable( false); JScrollPane textScroller = new JScrollPane( textPane); textRow.add( textScroller); textRow.add( Box.createRigidArea( new Dimension( 16, 0))); textDoc = textPane.getDocument(); JPanel addressRow = new JPanel(); addressRow.setLayout( new BoxLayout( addressRow, BoxLayout.X_AXIS)); targetPicker = new JComboBox( targetList); targetPicker.addItemListener( this); addressRow.add( Box.createRigidArea( new Dimension( 16, 0))); addressRow.add( new JLabel( "Talk to: ")); addressRow.add( targetPicker); addressRow.add( Box.createHorizontalGlue()); JPanel buttonRow = new JPanel(); buttonRow.setLayout( new BoxLayout( buttonRow, BoxLayout.X_AXIS)); buttonRow.add( Box.createRigidArea( new Dimension( 16, 0))); inputField = new JTextField(); // prevent inputField from hijacking key inputField.getKeymap().removeKeyStrokeBinding( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0)); buttonRow.add( inputField); sendButton = new JButton( "Send"); buttonRow.add( Box.createRigidArea( new Dimension( 8, 0))); buttonRow.add( sendButton); buttonRow.add( Box.createRigidArea( new Dimension( 16, 0))); rootPane.setDefaultButton( sendButton); sendButton.addActionListener( this); sendButton.setEnabled( false); parent.add( Box.createRigidArea( new Dimension( 0, 16))); parent.add( textRow); parent.add( Box.createRigidArea( new Dimension( 0, 8))); parent.add( addressRow); parent.add( Box.createRigidArea( new Dimension( 0, 8))); parent.add( buttonRow); parent.add( Box.createRigidArea( new Dimension( 0, 16))); } public void serviceRegistered( DNSSDRegistration registration, int flags, String serviceName, String regType, String domain) { ourName = serviceName; // might have been renamed on collision } public void operationFailed( DNSSDService service, int errorCode) { System.out.println( "Service reported error " + String.valueOf( errorCode)); } public void serviceResolved( DNSSDService resolver, int flags, int ifIndex, String fullName, String hostName, int port, TXTRecord txtRecord) { buddyPort = port; try { // Start a record query to obtain IP address from hostname DNSSD.queryRecord( 0, ifIndex, hostName, 1 /* ns_t_a */, 1 /* ns_c_in */, new SwingQueryListener( this)); } catch ( Exception e) { terminateWithException( e); } resolver.stop(); } public void queryAnswered( DNSSDService query, int flags, int ifIndex, String fullName, int rrtype, int rrclass, byte[] rdata, int ttl) { try { buddyAddr = InetAddress.getByAddress( rdata); } catch ( Exception e) { terminateWithException( e); } sendButton.setEnabled( true); } public void actionPerformed( ActionEvent e) // invoked when Send button is hit { try { String sendString = ourName + ": " + inputField.getText(); byte[] sendData = sendString.getBytes( kWireCharSet); outSocket.send( new DatagramPacket( sendData, sendData.length, buddyAddr, buddyPort)); StyleConstants.setForeground( textAttribs, Color.black); textDoc.insertString( textDoc.getLength(), inputField.getText() + "\n", textAttribs); inputField.setText( ""); } catch ( Exception exception) { terminateWithException( exception); } } public void itemStateChanged( ItemEvent e) // invoked when Target selection changes { sendButton.setEnabled( false); if ( e.getStateChange() == ItemEvent.SELECTED) { try { TargetListElem sel = (TargetListElem) targetList.getSelectedItem(); resolver = DNSSD.resolve( 0, sel.fInt, sel.fServiceName, sel.fType, sel.fDomain, this); } catch ( Exception exception) { terminateWithException( exception); } } } public void run() // invoked on event thread when inbound packet arrives { try { String inMessage = new String( dataPacket.getData(), 0, dataPacket.getLength(), kWireCharSet); StyleConstants.setForeground( textAttribs, this.getColorFor( dataPacket.getData(), dataPacket.getLength())); textDoc.insertString( textDoc.getLength(), inMessage + "\n", textAttribs); } catch ( Exception e) { terminateWithException( e); } } protected Color getColorFor( byte[] chars, int length) // Produce a mapping from a string to a color, suitable for text display { int rgb = 0; for ( int i=0; i < length && chars[i] != ':'; i++) rgb = rgb ^ ( (int) chars[i] << (i%3+2) * 8); return new Color( rgb & 0x007F7FFF); // mask off high bits so it is a dark color // for ( int i=0; i < length && chars[i] != ':'; i++) } protected static void terminateWithException( Exception e) { e.printStackTrace(); System.exit( -1); } public static void main(String s[]) { try { new SimpleChat(); } catch ( Exception e) { terminateWithException( e); } } } class TargetListElem { public TargetListElem( String serviceName, String domain, String type, int ifIndex) { fServiceName = serviceName; fDomain = domain; fType = type; fInt = ifIndex; } public String toString() { return fServiceName; } public String fServiceName, fDomain, fType; public int fInt; } class TargetListModel extends DefaultComboBoxModel implements BrowseListener { /* The Browser invokes this callback when a service is discovered. */ public void serviceFound( DNSSDService browser, int flags, int ifIndex, String serviceName, String regType, String domain) { TargetListElem match = this.findMatching( serviceName); // probably doesn't handle near-duplicates well. if ( match == null) this.addElement( new TargetListElem( serviceName, domain, regType, ifIndex)); } /* The Browser invokes this callback when a service disappears. */ public void serviceLost( DNSSDService browser, int flags, int ifIndex, String serviceName, String regType, String domain) { TargetListElem match = this.findMatching( serviceName); // probably doesn't handle near-duplicates well. if ( match != null) this.removeElement( match); } /* The Browser invokes this callback when a service disappears. */ public void operationFailed( DNSSDService service, int errorCode) { System.out.println( "Service reported error " + String.valueOf( errorCode)); } protected TargetListElem findMatching( String match) { for ( int i = 0; i < this.getSize(); i++) if ( match.equals( this.getElementAt( i).toString())) return (TargetListElem) this.getElementAt( i); return null; } } // A ListenerThread runs its owner when datagram packet p appears on socket s. class ListenerThread extends Thread { public ListenerThread( Runnable owner, DatagramSocket s, DatagramPacket p) { fOwner = owner; fSocket = s; fPacket = p; } public void run() { while ( true ) { try { fSocket.receive( fPacket); SwingUtilities.invokeAndWait( fOwner); // process data on main thread } catch( Exception e) { break; // terminate thread } } } protected Runnable fOwner; protected DatagramSocket fSocket; protected DatagramPacket fPacket; }