]> git.saurik.com Git - apple/mdnsresponder.git/blob - Clients/Java/SimpleChat.java
mDNSResponder-66.3.tar.gz
[apple/mdnsresponder.git] / Clients / Java / SimpleChat.java
1 /*
2 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
7 *
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24
25 Change History (most recent first):
26
27 $Log: SimpleChat.java,v $
28 Revision 1.2 2004/04/30 21:53:35 rpantos
29 Change line endings for CVS.
30
31 Revision 1.1 2004/04/30 16:29:35 rpantos
32 First checked in.
33
34 SimpleChat is a simple peer-to-peer chat program that demonstrates
35 DNSSD registration, browsing, resolving and record-querying.
36
37 To do:
38 - remove TXTRecord tests
39 - remove diagnostic printf's
40 - implement better coloring algorithm
41 */
42
43
44 import java.awt.*;
45 import java.awt.event.*;
46 import java.text.*;
47 import java.net.*;
48 import javax.swing.*;
49 import javax.swing.event.*;
50 import javax.swing.text.*;
51
52 import com.apple.dnssd.*;
53
54
55 class SimpleChat implements ResolveListener, RegisterListener, QueryListener,
56 ActionListener, ItemListener, Runnable
57 {
58 Document textDoc; // Holds all the chat text
59 JTextField inputField; // Holds a pending chat response
60 String ourName; // name used to identify this user in chat
61 DNSSDService browser; // object that actively browses for other chat clients
62 DNSSDService resolver; // object that resolves other chat clients
63 DNSSDRegistration registration; // object that maintains our connection advertisement
64 JComboBox targetPicker; // Indicates who we're talking to
65 TargetListModel targetList; // and its list model
66 JButton sendButton; // Will send text in inputField to target
67 InetAddress buddyAddr; // and address
68 int buddyPort; // and port
69 DatagramPacket dataPacket; // Inbound data packet
70 DatagramSocket outSocket; // Outbound data socket
71 SimpleAttributeSet textAttribs;
72
73 static final String kChatExampleRegType = "_p2pchat._udp";
74 static final String kWireCharSet = "ISO-8859-1";
75
76 public SimpleChat() throws Exception
77 {
78 JFrame frame = new JFrame("SimpleChat");
79 frame.addWindowListener(new WindowAdapter() {
80 public void windowClosing(WindowEvent e) {System.exit(0);}
81 });
82
83 ourName = System.getProperty( "user.name");
84 targetList = new TargetListModel();
85 textAttribs = new SimpleAttributeSet();
86 DatagramSocket inSocket = new DatagramSocket();
87 dataPacket = new DatagramPacket( new byte[ 4096], 4096);
88 outSocket = new DatagramSocket();
89
90 this.setupSubPanes( frame.getContentPane(), frame.getRootPane());
91 frame.pack();
92 frame.setVisible(true);
93 inputField.requestFocusInWindow();
94
95 browser = DNSSD.browse( 0, 0, kChatExampleRegType, "", new SwingBrowseListener( targetList));
96
97 TXTRecord tRec = new TXTRecord();
98 tRec.set( "name", "roger");
99 tRec.set( "color", "blue");
100
101 registration = DNSSD.register( 0, 0, ourName, kChatExampleRegType, "", "", inSocket.getLocalPort(), tRec, 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 System.out.println( "ServiceRegisterCallback() invoked as " + serviceName);
159 }
160
161 public void operationFailed( DNSSDService service, int errorCode)
162 {
163 System.out.println( "Service reported error " + String.valueOf( errorCode));
164 }
165
166 public void serviceResolved( DNSSDService resolver, int flags, int ifIndex, String fullName,
167 String hostName, int port, TXTRecord txtRecord)
168 {
169 String a;
170 for ( int i=0; null != ( a = txtRecord.getKey( i)); i++)
171 System.out.println( "attr/val " + String.valueOf( i) + ": " + a + "," + txtRecord.getValueAsString( i));
172
173 buddyPort = port;
174 try {
175 // Start a record query to obtain IP address from hostname
176 DNSSD.queryRecord( 0, ifIndex, hostName, 1 /* ns_t_a */, 1 /* ns_c_in */,
177 new SwingQueryListener( this));
178 }
179 catch ( Exception e) { terminateWithException( e); }
180 resolver.stop();
181 }
182
183 public void queryAnswered( DNSSDService query, int flags, int ifIndex, String fullName,
184 int rrtype, int rrclass, byte[] rdata, int ttl)
185 {
186 try {
187 buddyAddr = InetAddress.getByAddress( rdata);
188 }
189 catch ( Exception e) { terminateWithException( e); }
190 sendButton.setEnabled( true);
191 }
192
193 public void actionPerformed( ActionEvent e) // invoked when Send button is hit
194 {
195 try
196 {
197 String sendString = ourName + ": " + inputField.getText();
198 byte[] sendData = sendString.getBytes( kWireCharSet);
199 outSocket.send( new DatagramPacket( sendData, sendData.length, buddyAddr, buddyPort));
200 StyleConstants.setForeground( textAttribs, Color.black);
201 textDoc.insertString( textDoc.getLength(), inputField.getText() + "\n", textAttribs);
202 inputField.setText( "");
203 }
204 catch ( Exception exception) { terminateWithException( exception); }
205 }
206
207 public void itemStateChanged( ItemEvent e) // invoked when Target selection changes
208 {
209 sendButton.setEnabled( false);
210 if ( e.getStateChange() == ItemEvent.SELECTED)
211 {
212 try {
213 TargetListElem sel = (TargetListElem) targetList.getSelectedItem();
214 resolver = DNSSD.resolve( 0, sel.fInt, sel.fServiceName, sel.fType, sel.fDomain, this);
215 }
216 catch ( Exception exception) { terminateWithException( exception); }
217 }
218 }
219
220 public void run() // invoked on event thread when inbound packet arrives
221 {
222 try
223 {
224 String inMessage = new String( dataPacket.getData(), 0, dataPacket.getLength(), kWireCharSet);
225 StyleConstants.setForeground( textAttribs, this.getColorFor( dataPacket.getData(), dataPacket.getLength()));
226 textDoc.insertString( textDoc.getLength(), inMessage + "\n", textAttribs);
227 }
228 catch ( Exception e) { terminateWithException( e); }
229 }
230
231 protected Color getColorFor( byte[] chars, int length)
232 // Produce a mapping from a string to a color, suitable for text display
233 {
234 int rgb = 0;
235 for ( int i=0; i < length && chars[i] != ':'; i++)
236 rgb = rgb ^ ( (int) chars[i] << (i%3+2) * 8);
237 return new Color( rgb & 0x007F7FFF); // mask off high bits so it is a dark color
238
239 // for ( int i=0; i < length && chars[i] != ':'; i++)
240
241 }
242
243 protected static void terminateWithException( Exception e)
244 {
245 e.printStackTrace();
246 System.exit( -1);
247 }
248
249 public static void main(String s[])
250 {
251 try {
252 new SimpleChat();
253 }
254 catch ( Exception e) { terminateWithException( e); }
255 }
256 }
257
258
259
260 class TargetListElem
261 {
262 public TargetListElem( String serviceName, String domain, String type, int ifIndex)
263 { fServiceName = serviceName; fDomain = domain; fType = type; fInt = ifIndex; }
264
265 public String toString() { return fServiceName; }
266
267 public String fServiceName, fDomain, fType;
268 public int fInt;
269 }
270
271 class TargetListModel extends DefaultComboBoxModel implements BrowseListener
272 {
273 /* The Browser invokes this callback when a service is discovered. */
274 public void serviceFound( DNSSDService browser, int flags, int ifIndex,
275 String serviceName, String regType, String domain)
276 {
277 TargetListElem match = this.findMatching( serviceName); // probably doesn't handle near-duplicates well.
278
279 if ( match == null)
280 this.addElement( new TargetListElem( serviceName, domain, regType, ifIndex));
281 }
282
283 /* The Browser invokes this callback when a service disappears. */
284 public void serviceLost( DNSSDService browser, int flags, int ifIndex,
285 String serviceName, String regType, String domain)
286 {
287 TargetListElem match = this.findMatching( serviceName); // probably doesn't handle near-duplicates well.
288
289 if ( match != null)
290 this.removeElement( match);
291 }
292
293 /* The Browser invokes this callback when a service disappears. */
294 public void operationFailed( DNSSDService service, int errorCode)
295 {
296 System.out.println( "Service reported error " + String.valueOf( errorCode));
297 }
298
299 protected TargetListElem findMatching( String match)
300 {
301 for ( int i = 0; i < this.getSize(); i++)
302 if ( match.equals( this.getElementAt( i).toString()))
303 return (TargetListElem) this.getElementAt( i);
304 return null;
305 }
306
307 }
308
309
310 // A ListenerThread runs its owner when datagram packet p appears on socket s.
311 class ListenerThread extends Thread
312 {
313 public ListenerThread( Runnable owner, DatagramSocket s, DatagramPacket p)
314 { fOwner = owner; fSocket = s; fPacket = p; }
315
316 public void run()
317 {
318 while ( true )
319 {
320 try
321 {
322 fSocket.receive( fPacket);
323 SwingUtilities.invokeAndWait( fOwner); // process data on main thread
324 }
325 catch( Exception e)
326 {
327 break; // terminate thread
328 }
329 }
330 }
331
332 protected Runnable fOwner;
333 protected DatagramSocket fSocket;
334 protected DatagramPacket fPacket;
335 }
336
337
338