Sebastian Kuschel reports that pfd_closing can be called for a socket
[u/mdw/putty] / macosx / osxsel.m
CommitLineData
1ddda1ca 1/*
2 * osxsel.m: OS X implementation of the front end interface to uxsel.
3 */
4
5#import <Cocoa/Cocoa.h>
6#include <unistd.h>
7#include "putty.h"
8#include "osxclass.h"
9
10/*
11 * The unofficial Cocoa FAQ at
12 *
13 * http://www.alastairs-place.net/cocoa/faq.txt
14 *
15 * says that Cocoa has the native ability to be given an fd and
16 * tell you when it becomes readable, but cannot tell you when it
17 * becomes _writable_. This is unacceptable to PuTTY, which depends
18 * for correct functioning on being told both. Therefore, I can't
19 * use the Cocoa native mechanism.
20 *
21 * Instead, I'm going to resort to threads. I start a second thread
22 * whose job is to do selects. At the termination of every select,
23 * it posts a Cocoa event into the main thread's event queue, so
24 * that the main thread gets select results interleaved with other
25 * GUI operations. Communication from the main thread _to_ the
26 * select thread is performed by writing to a pipe whose other end
27 * is one of the file descriptors being selected on. (This is the
28 * only sensible way, because we have to be able to interrupt a
29 * select in order to provide a new fd list.)
30 */
31
32/*
33 * In more detail, the select thread must:
34 *
35 * - start off by listening to _just_ the pipe, waiting to be told
36 * to begin a select.
37 *
38 * - when it receives the `start' command, it should read the
39 * shared uxsel data (which is protected by a mutex), set up its
40 * select, and begin it.
41 *
42 * - when the select terminates, it should write the results
43 * (perhaps minus the inter-thread pipe if it's there) into
44 * shared memory and dispatch a GUI event to let the main thread
45 * know.
46 *
47 * - the main thread will then think about it, do some processing,
48 * and _then_ send a command saying `now restart select'. Before
49 * sending that command it might easily have tinkered with the
50 * uxsel structures, which is why it waited before sending it.
51 *
52 * - EOF on the inter-thread pipe, of course, means the process
53 * has finished completely, so the select thread terminates.
54 *
55 * - The main thread may wish to adjust the uxsel settings in the
56 * middle of a select. In this situation it first writes the new
57 * data to the shared memory area, then notifies the select
58 * thread by writing to the inter-thread pipe.
59 *
60 * So the upshot is that the sequence of operations performed in
61 * the select thread must be:
62 *
63 * - read a byte from the pipe (which may block)
64 *
65 * - read the shared uxsel data and perform a select
66 *
67 * - notify the main thread of interesting select results (if any)
68 *
69 * - loop round again from the top.
70 *
71 * This is sufficient. Notifying the select thread asynchronously
72 * by writing to the pipe will cause its select to terminate and
73 * another to begin immediately without blocking. If the select
74 * thread's select terminates due to network data, its subsequent
75 * pipe read will block until the main thread is ready to let it
76 * loose again.
77 */
78
79static int osxsel_pipe[2];
80
81static NSLock *osxsel_inlock;
82static fd_set osxsel_rfds_in;
83static fd_set osxsel_wfds_in;
84static fd_set osxsel_xfds_in;
85static int osxsel_inmax;
86
87static NSLock *osxsel_outlock;
88static fd_set osxsel_rfds_out;
89static fd_set osxsel_wfds_out;
90static fd_set osxsel_xfds_out;
91static int osxsel_outmax;
92
93static int inhibit_start_select;
94
95/*
96 * NSThread requires an object method as its thread procedure, so
97 * here I define a trivial holding class.
98 */
99@class OSXSel;
100@interface OSXSel : NSObject
101{
102}
103- (void)runThread:(id)arg;
104@end
105@implementation OSXSel
106- (void)runThread:(id)arg
107{
108 char c;
109 fd_set r, w, x;
110 int n, ret;
111 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
112
113 while (1) {
114 /*
115 * Read one byte from the pipe.
116 */
117 ret = read(osxsel_pipe[0], &c, 1);
118
119 if (ret <= 0)
120 return; /* terminate the thread */
121
122 /*
123 * Now set up the select data.
124 */
125 [osxsel_inlock lock];
126 memcpy(&r, &osxsel_rfds_in, sizeof(fd_set));
127 memcpy(&w, &osxsel_wfds_in, sizeof(fd_set));
128 memcpy(&x, &osxsel_xfds_in, sizeof(fd_set));
129 n = osxsel_inmax;
130 [osxsel_inlock unlock];
131 FD_SET(osxsel_pipe[0], &r);
132 if (n < osxsel_pipe[0]+1)
133 n = osxsel_pipe[0]+1;
134
135 /*
136 * Perform the select.
137 */
138 ret = select(n, &r, &w, &x, NULL);
139
140 /*
141 * Detect the one special case in which the only
142 * interesting fd was the inter-thread pipe. In that
143 * situation only we are interested - the main thread will
144 * not be!
145 */
146 if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r))
147 continue; /* just loop round again */
148
149 /*
150 * Write the select results to shared data.
151 *
152 * I _think_ we don't need this data to be lock-protected:
153 * it won't be read by the main thread until after we send
154 * a message indicating that we've finished writing it, and
155 * we won't start another select (hence potentially writing
156 * it again) until the main thread notifies us in return.
157 *
158 * However, I'm scared of multithreading and not totally
159 * convinced of my reasoning, so I'm going to lock it
160 * anyway.
161 */
162 [osxsel_outlock lock];
163 memcpy(&osxsel_rfds_out, &r, sizeof(fd_set));
164 memcpy(&osxsel_wfds_out, &w, sizeof(fd_set));
165 memcpy(&osxsel_xfds_out, &x, sizeof(fd_set));
166 osxsel_outmax = n;
167 [osxsel_outlock unlock];
168
169 /*
170 * Post a message to the main thread's message queue
171 * telling it that select data is available.
172 */
173 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
174 location:NSMakePoint(0,0)
175 modifierFlags:0
176 timestamp:0
177 windowNumber:0
178 context:nil
179 subtype:0
180 data1:0
181 data2:0]
182 atStart:NO];
183 }
184
185 [pool release];
186}
187@end
188
189void osxsel_init(void)
190{
191 uxsel_init();
192
193 if (pipe(osxsel_pipe) < 0) {
194 fatalbox("Unable to set up inter-thread pipe for select");
195 }
196 [NSThread detachNewThreadSelector:@selector(runThread:)
197 toTarget:[[[OSXSel alloc] init] retain] withObject:nil];
198 /*
199 * Also initialise (i.e. clear) the input fd_sets. Need not
200 * start a select just yet - the select thread will block until
201 * we have at least one fd for it!
202 */
203 FD_ZERO(&osxsel_rfds_in);
204 FD_ZERO(&osxsel_wfds_in);
205 FD_ZERO(&osxsel_xfds_in);
206 osxsel_inmax = 0;
207 /*
208 * Initialise the mutex locks used to protect the data passed
209 * between threads.
210 */
211 osxsel_inlock = [[[NSLock alloc] init] retain];
212 osxsel_outlock = [[[NSLock alloc] init] retain];
213}
214
215static void osxsel_start_select(void)
216{
217 char c = 'g'; /* for `Go!' :-) but it's never used */
218
219 if (!inhibit_start_select)
220 write(osxsel_pipe[1], &c, 1);
221}
222
223int uxsel_input_add(int fd, int rwx)
224{
225 /*
226 * Add the new fd to the appropriate input fd_sets, then write
227 * to the inter-thread pipe.
228 */
229 [osxsel_inlock lock];
230 if (rwx & 1)
231 FD_SET(fd, &osxsel_rfds_in);
232 else
233 FD_CLR(fd, &osxsel_rfds_in);
234 if (rwx & 2)
235 FD_SET(fd, &osxsel_wfds_in);
236 else
237 FD_CLR(fd, &osxsel_wfds_in);
238 if (rwx & 4)
239 FD_SET(fd, &osxsel_xfds_in);
240 else
241 FD_CLR(fd, &osxsel_xfds_in);
242 if (osxsel_inmax < fd+1)
243 osxsel_inmax = fd+1;
244 [osxsel_inlock unlock];
245 osxsel_start_select();
246
247 /*
248 * We must return an `id' which will be passed back to us at
249 * the time of uxsel_input_remove. Since we have no need to
250 * store ids in that sense, we might as well go with the fd
251 * itself.
252 */
253 return fd;
254}
255
256void uxsel_input_remove(int id)
257{
258 /*
259 * Remove the fd from all the input fd_sets. In this
260 * implementation, the simplest way to do that is to call
261 * uxsel_input_add with rwx==0!
262 */
263 uxsel_input_add(id, 0);
264}
265
266/*
267 * Function called in the main thread to process results. It will
268 * have to read the output fd_sets, go through them, call back to
269 * uxsel with the results, and then write to the inter-thread pipe.
270 *
271 * This function will have to be called from an event handler in
272 * osxmain.m, which will therefore necessarily contain a small part
273 * of this mechanism (along with calling osxsel_init).
274 */
275void osxsel_process_results(void)
276{
277 int i;
278
279 /*
280 * We must write to the pipe to start a fresh select _even if_
281 * there were no changes. So for efficiency, we set a flag here
282 * which inhibits uxsel_input_{add,remove} from writing to the
283 * pipe; then once we finish processing, we clear the flag
284 * again and write a single byte ourselves. It's cleaner,
285 * because it wakes up the select thread fewer times.
286 */
287 inhibit_start_select = TRUE;
288
289 [osxsel_outlock lock];
290
291 for (i = 0; i < osxsel_outmax; i++) {
292 if (FD_ISSET(i, &osxsel_xfds_out))
293 select_result(i, 4);
294 }
295 for (i = 0; i < osxsel_outmax; i++) {
296 if (FD_ISSET(i, &osxsel_rfds_out))
297 select_result(i, 1);
298 }
299 for (i = 0; i < osxsel_outmax; i++) {
300 if (FD_ISSET(i, &osxsel_wfds_out))
301 select_result(i, 2);
302 }
303
304 [osxsel_outlock unlock];
305
306 inhibit_start_select = FALSE;
307 osxsel_start_select();
308}