]>
Commit | Line | Data |
---|---|---|
67c8f8a1 A |
1 | /* -*- Mode: Java; tab-width: 4 -*- |
2 | * | |
8e92c31c A |
3 | * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. |
4 | * | |
7f0064bd A |
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. | |
8e92c31c A |
39 | |
40 | SimpleChat is a simple peer-to-peer chat program that demonstrates | |
7f0064bd | 41 | DNS-SD registration, browsing, resolving and record-querying. |
8e92c31c A |
42 | |
43 | To do: | |
8e92c31c A |
44 | - implement better coloring algorithm |
45 | */ | |
46 | ||
47 | ||
48 | import java.awt.*; | |
49 | import java.awt.event.*; | |
50 | import java.text.*; | |
51 | import java.net.*; | |
52 | import javax.swing.*; | |
53 | import javax.swing.event.*; | |
54 | import javax.swing.text.*; | |
55 | ||
56 | import com.apple.dnssd.*; | |
57 | ||
58 | ||
59 | class 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 | ||
7f0064bd | 101 | registration = DNSSD.register( 0, 0, ourName, kChatExampleRegType, "", "", inSocket.getLocalPort(), null, this); |
8e92c31c A |
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 | |
8e92c31c A |
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 | { | |
8e92c31c A |
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 | ||
255 | class 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 | ||
266 | class 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. | |
306 | class 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 |