]>
Commit | Line | Data |
---|---|---|
8e92c31c A |
1 | /* |
2 | * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. | |
3 | * | |
7f0064bd A |
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. | |
8e92c31c A |
38 | |
39 | SimpleChat is a simple peer-to-peer chat program that demonstrates | |
7f0064bd | 40 | DNS-SD registration, browsing, resolving and record-querying. |
8e92c31c A |
41 | |
42 | To do: | |
8e92c31c A |
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 | ||
7f0064bd | 100 | registration = DNSSD.register( 0, 0, ourName, kChatExampleRegType, "", "", inSocket.getLocalPort(), null, this); |
8e92c31c A |
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 | |
8e92c31c A |
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 | { | |
8e92c31c A |
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 |