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