2 * osxsel.m: OS X implementation of the front end interface to uxsel.
\r
5 #import <Cocoa/Cocoa.h>
\r
8 #include "osxclass.h"
\r
11 * The unofficial Cocoa FAQ at
\r
13 * http://www.alastairs-place.net/cocoa/faq.txt
\r
15 * says that Cocoa has the native ability to be given an fd and
\r
16 * tell you when it becomes readable, but cannot tell you when it
\r
17 * becomes _writable_. This is unacceptable to PuTTY, which depends
\r
18 * for correct functioning on being told both. Therefore, I can't
\r
19 * use the Cocoa native mechanism.
\r
21 * Instead, I'm going to resort to threads. I start a second thread
\r
22 * whose job is to do selects. At the termination of every select,
\r
23 * it posts a Cocoa event into the main thread's event queue, so
\r
24 * that the main thread gets select results interleaved with other
\r
25 * GUI operations. Communication from the main thread _to_ the
\r
26 * select thread is performed by writing to a pipe whose other end
\r
27 * is one of the file descriptors being selected on. (This is the
\r
28 * only sensible way, because we have to be able to interrupt a
\r
29 * select in order to provide a new fd list.)
\r
33 * In more detail, the select thread must:
\r
35 * - start off by listening to _just_ the pipe, waiting to be told
\r
36 * to begin a select.
\r
38 * - when it receives the `start' command, it should read the
\r
39 * shared uxsel data (which is protected by a mutex), set up its
\r
40 * select, and begin it.
\r
42 * - when the select terminates, it should write the results
\r
43 * (perhaps minus the inter-thread pipe if it's there) into
\r
44 * shared memory and dispatch a GUI event to let the main thread
\r
47 * - the main thread will then think about it, do some processing,
\r
48 * and _then_ send a command saying `now restart select'. Before
\r
49 * sending that command it might easily have tinkered with the
\r
50 * uxsel structures, which is why it waited before sending it.
\r
52 * - EOF on the inter-thread pipe, of course, means the process
\r
53 * has finished completely, so the select thread terminates.
\r
55 * - The main thread may wish to adjust the uxsel settings in the
\r
56 * middle of a select. In this situation it first writes the new
\r
57 * data to the shared memory area, then notifies the select
\r
58 * thread by writing to the inter-thread pipe.
\r
60 * So the upshot is that the sequence of operations performed in
\r
61 * the select thread must be:
\r
63 * - read a byte from the pipe (which may block)
\r
65 * - read the shared uxsel data and perform a select
\r
67 * - notify the main thread of interesting select results (if any)
\r
69 * - loop round again from the top.
\r
71 * This is sufficient. Notifying the select thread asynchronously
\r
72 * by writing to the pipe will cause its select to terminate and
\r
73 * another to begin immediately without blocking. If the select
\r
74 * thread's select terminates due to network data, its subsequent
\r
75 * pipe read will block until the main thread is ready to let it
\r
79 static int osxsel_pipe[2];
\r
81 static NSLock *osxsel_inlock;
\r
82 static fd_set osxsel_rfds_in;
\r
83 static fd_set osxsel_wfds_in;
\r
84 static fd_set osxsel_xfds_in;
\r
85 static int osxsel_inmax;
\r
87 static NSLock *osxsel_outlock;
\r
88 static fd_set osxsel_rfds_out;
\r
89 static fd_set osxsel_wfds_out;
\r
90 static fd_set osxsel_xfds_out;
\r
91 static int osxsel_outmax;
\r
93 static int inhibit_start_select;
\r
96 * NSThread requires an object method as its thread procedure, so
\r
97 * here I define a trivial holding class.
\r
100 @interface OSXSel : NSObject
\r
103 - (void)runThread:(id)arg;
\r
105 @implementation OSXSel
\r
106 - (void)runThread:(id)arg
\r
111 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
\r
115 * Read one byte from the pipe.
\r
117 ret = read(osxsel_pipe[0], &c, 1);
\r
120 return; /* terminate the thread */
\r
123 * Now set up the select data.
\r
125 [osxsel_inlock lock];
\r
126 memcpy(&r, &osxsel_rfds_in, sizeof(fd_set));
\r
127 memcpy(&w, &osxsel_wfds_in, sizeof(fd_set));
\r
128 memcpy(&x, &osxsel_xfds_in, sizeof(fd_set));
\r
130 [osxsel_inlock unlock];
\r
131 FD_SET(osxsel_pipe[0], &r);
\r
132 if (n < osxsel_pipe[0]+1)
\r
133 n = osxsel_pipe[0]+1;
\r
136 * Perform the select.
\r
138 ret = select(n, &r, &w, &x, NULL);
\r
141 * Detect the one special case in which the only
\r
142 * interesting fd was the inter-thread pipe. In that
\r
143 * situation only we are interested - the main thread will
\r
146 if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r))
\r
147 continue; /* just loop round again */
\r
150 * Write the select results to shared data.
\r
152 * I _think_ we don't need this data to be lock-protected:
\r
153 * it won't be read by the main thread until after we send
\r
154 * a message indicating that we've finished writing it, and
\r
155 * we won't start another select (hence potentially writing
\r
156 * it again) until the main thread notifies us in return.
\r
158 * However, I'm scared of multithreading and not totally
\r
159 * convinced of my reasoning, so I'm going to lock it
\r
162 [osxsel_outlock lock];
\r
163 memcpy(&osxsel_rfds_out, &r, sizeof(fd_set));
\r
164 memcpy(&osxsel_wfds_out, &w, sizeof(fd_set));
\r
165 memcpy(&osxsel_xfds_out, &x, sizeof(fd_set));
\r
167 [osxsel_outlock unlock];
\r
170 * Post a message to the main thread's message queue
\r
171 * telling it that select data is available.
\r
173 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
\r
174 location:NSMakePoint(0,0)
\r
189 void osxsel_init(void)
\r
193 if (pipe(osxsel_pipe) < 0) {
\r
194 fatalbox("Unable to set up inter-thread pipe for select");
\r
196 [NSThread detachNewThreadSelector:@selector(runThread:)
\r
197 toTarget:[[[OSXSel alloc] init] retain] withObject:nil];
\r
199 * Also initialise (i.e. clear) the input fd_sets. Need not
\r
200 * start a select just yet - the select thread will block until
\r
201 * we have at least one fd for it!
\r
203 FD_ZERO(&osxsel_rfds_in);
\r
204 FD_ZERO(&osxsel_wfds_in);
\r
205 FD_ZERO(&osxsel_xfds_in);
\r
208 * Initialise the mutex locks used to protect the data passed
\r
211 osxsel_inlock = [[[NSLock alloc] init] retain];
\r
212 osxsel_outlock = [[[NSLock alloc] init] retain];
\r
215 static void osxsel_start_select(void)
\r
217 char c = 'g'; /* for `Go!' :-) but it's never used */
\r
219 if (!inhibit_start_select)
\r
220 write(osxsel_pipe[1], &c, 1);
\r
223 int uxsel_input_add(int fd, int rwx)
\r
226 * Add the new fd to the appropriate input fd_sets, then write
\r
227 * to the inter-thread pipe.
\r
229 [osxsel_inlock lock];
\r
231 FD_SET(fd, &osxsel_rfds_in);
\r
233 FD_CLR(fd, &osxsel_rfds_in);
\r
235 FD_SET(fd, &osxsel_wfds_in);
\r
237 FD_CLR(fd, &osxsel_wfds_in);
\r
239 FD_SET(fd, &osxsel_xfds_in);
\r
241 FD_CLR(fd, &osxsel_xfds_in);
\r
242 if (osxsel_inmax < fd+1)
\r
243 osxsel_inmax = fd+1;
\r
244 [osxsel_inlock unlock];
\r
245 osxsel_start_select();
\r
248 * We must return an `id' which will be passed back to us at
\r
249 * the time of uxsel_input_remove. Since we have no need to
\r
250 * store ids in that sense, we might as well go with the fd
\r
256 void uxsel_input_remove(int id)
\r
259 * Remove the fd from all the input fd_sets. In this
\r
260 * implementation, the simplest way to do that is to call
\r
261 * uxsel_input_add with rwx==0!
\r
263 uxsel_input_add(id, 0);
\r
267 * Function called in the main thread to process results. It will
\r
268 * have to read the output fd_sets, go through them, call back to
\r
269 * uxsel with the results, and then write to the inter-thread pipe.
\r
271 * This function will have to be called from an event handler in
\r
272 * osxmain.m, which will therefore necessarily contain a small part
\r
273 * of this mechanism (along with calling osxsel_init).
\r
275 void osxsel_process_results(void)
\r
280 * We must write to the pipe to start a fresh select _even if_
\r
281 * there were no changes. So for efficiency, we set a flag here
\r
282 * which inhibits uxsel_input_{add,remove} from writing to the
\r
283 * pipe; then once we finish processing, we clear the flag
\r
284 * again and write a single byte ourselves. It's cleaner,
\r
285 * because it wakes up the select thread fewer times.
\r
287 inhibit_start_select = TRUE;
\r
289 [osxsel_outlock lock];
\r
291 for (i = 0; i < osxsel_outmax; i++) {
\r
292 if (FD_ISSET(i, &osxsel_xfds_out))
\r
293 select_result(i, 4);
\r
295 for (i = 0; i < osxsel_outmax; i++) {
\r
296 if (FD_ISSET(i, &osxsel_rfds_out))
\r
297 select_result(i, 1);
\r
299 for (i = 0; i < osxsel_outmax; i++) {
\r
300 if (FD_ISSET(i, &osxsel_wfds_out))
\r
301 select_result(i, 2);
\r
304 [osxsel_outlock unlock];
\r
306 inhibit_start_select = FALSE;
\r
307 osxsel_start_select();
\r