]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - macosx/osxsel.m
Allow PROCESS_QUERY_INFORMATION access to our process.
[PuTTY.git] / macosx / osxsel.m
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
79 static int osxsel_pipe[2];
80
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;
86
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;
92
93 static 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
189 void 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
215 static 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
223 int 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
256 void 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  */
275 void 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 }