2 * osxsel.m: OS X implementation of the front end interface to uxsel.
5 #import <Cocoa/Cocoa.h>
11 * The unofficial Cocoa FAQ at
13 * http://www.alastairs-place.net/cocoa/faq.txt
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.
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.)
33 * In more detail, the select thread must:
35 * - start off by listening to _just_ the pipe, waiting to be told
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.
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
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.
52 * - EOF on the inter-thread pipe, of course, means the process
53 * has finished completely, so the select thread terminates.
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.
60 * So the upshot is that the sequence of operations performed in
61 * the select thread must be:
63 * - read a byte from the pipe (which may block)
65 * - read the shared uxsel data and perform a select
67 * - notify the main thread of interesting select results (if any)
69 * - loop round again from the top.
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
79 static int osxsel_pipe[2];
81 static NSLock *osxsel_inlock;
82 static fd_set osxsel_rfds_in;
83 static fd_set osxsel_wfds_in;
84 static fd_set osxsel_xfds_in;
85 static int osxsel_inmax;
87 static NSLock *osxsel_outlock;
88 static fd_set osxsel_rfds_out;
89 static fd_set osxsel_wfds_out;
90 static fd_set osxsel_xfds_out;
91 static int osxsel_outmax;
93 static int inhibit_start_select;
96 * NSThread requires an object method as its thread procedure, so
97 * here I define a trivial holding class.
100 @interface OSXSel : NSObject
103 - (void)runThread:(id)arg;
105 @implementation OSXSel
106 - (void)runThread:(id)arg
111 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
115 * Read one byte from the pipe.
117 ret = read(osxsel_pipe[0], &c, 1);
120 return; /* terminate the thread */
123 * Now set up the select data.
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));
130 [osxsel_inlock unlock];
131 FD_SET(osxsel_pipe[0], &r);
132 if (n < osxsel_pipe[0]+1)
133 n = osxsel_pipe[0]+1;
136 * Perform the select.
138 ret = select(n, &r, &w, &x, NULL);
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
146 if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r))
147 continue; /* just loop round again */
150 * Write the select results to shared data.
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.
158 * However, I'm scared of multithreading and not totally
159 * convinced of my reasoning, so I'm going to lock it
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));
167 [osxsel_outlock unlock];
170 * Post a message to the main thread's message queue
171 * telling it that select data is available.
173 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
174 location:NSMakePoint(0,0)
189 void osxsel_init(void)
193 if (pipe(osxsel_pipe) < 0) {
194 fatalbox("Unable to set up inter-thread pipe for select");
196 [NSThread detachNewThreadSelector:@selector(runThread:)
197 toTarget:[[[OSXSel alloc] init] retain] withObject:nil];
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!
203 FD_ZERO(&osxsel_rfds_in);
204 FD_ZERO(&osxsel_wfds_in);
205 FD_ZERO(&osxsel_xfds_in);
208 * Initialise the mutex locks used to protect the data passed
211 osxsel_inlock = [[[NSLock alloc] init] retain];
212 osxsel_outlock = [[[NSLock alloc] init] retain];
215 static void osxsel_start_select(void)
217 char c = 'g'; /* for `Go!' :-) but it's never used */
219 if (!inhibit_start_select)
220 write(osxsel_pipe[1], &c, 1);
223 int uxsel_input_add(int fd, int rwx)
226 * Add the new fd to the appropriate input fd_sets, then write
227 * to the inter-thread pipe.
229 [osxsel_inlock lock];
231 FD_SET(fd, &osxsel_rfds_in);
233 FD_CLR(fd, &osxsel_rfds_in);
235 FD_SET(fd, &osxsel_wfds_in);
237 FD_CLR(fd, &osxsel_wfds_in);
239 FD_SET(fd, &osxsel_xfds_in);
241 FD_CLR(fd, &osxsel_xfds_in);
242 if (osxsel_inmax < fd+1)
244 [osxsel_inlock unlock];
245 osxsel_start_select();
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
256 void uxsel_input_remove(int id)
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!
263 uxsel_input_add(id, 0);
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.
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).
275 void osxsel_process_results(void)
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.
287 inhibit_start_select = TRUE;
289 [osxsel_outlock lock];
291 for (i = 0; i < osxsel_outmax; i++) {
292 if (FD_ISSET(i, &osxsel_xfds_out))
295 for (i = 0; i < osxsel_outmax; i++) {
296 if (FD_ISSET(i, &osxsel_rfds_out))
299 for (i = 0; i < osxsel_outmax; i++) {
300 if (FD_ISSET(i, &osxsel_wfds_out))
304 [osxsel_outlock unlock];
306 inhibit_start_select = FALSE;
307 osxsel_start_select();