OSDN Git Service

Modify documents for 1.98d.
[ffftp/ffftp.git] / putty / MACOSX / OSXSEL.M
1 /*\r
2  * osxsel.m: OS X implementation of the front end interface to uxsel.\r
3  */\r
4 \r
5 #import <Cocoa/Cocoa.h>\r
6 #include <unistd.h>\r
7 #include "putty.h"\r
8 #include "osxclass.h"\r
9 \r
10 /*\r
11  * The unofficial Cocoa FAQ at\r
12  *\r
13  *   http://www.alastairs-place.net/cocoa/faq.txt\r
14  * \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
20  * \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
30  */\r
31 \r
32 /*\r
33  * In more detail, the select thread must:\r
34  * \r
35  *  - start off by listening to _just_ the pipe, waiting to be told\r
36  *    to begin a select.\r
37  * \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
41  * \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
45  *    know.\r
46  * \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
51  * \r
52  *  - EOF on the inter-thread pipe, of course, means the process\r
53  *    has finished completely, so the select thread terminates.\r
54  * \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
59  * \r
60  * So the upshot is that the sequence of operations performed in\r
61  * the select thread must be:\r
62  * \r
63  *  - read a byte from the pipe (which may block)\r
64  * \r
65  *  - read the shared uxsel data and perform a select\r
66  * \r
67  *  - notify the main thread of interesting select results (if any)\r
68  * \r
69  *  - loop round again from the top.\r
70  * \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
76  * loose again.\r
77  */\r
78 \r
79 static int osxsel_pipe[2];\r
80 \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
86 \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
92 \r
93 static int inhibit_start_select;\r
94 \r
95 /*\r
96  * NSThread requires an object method as its thread procedure, so\r
97  * here I define a trivial holding class.\r
98  */\r
99 @class OSXSel;\r
100 @interface OSXSel : NSObject\r
101 {\r
102 }\r
103 - (void)runThread:(id)arg;\r
104 @end\r
105 @implementation OSXSel\r
106 - (void)runThread:(id)arg\r
107 {\r
108     char c;\r
109     fd_set r, w, x;\r
110     int n, ret;\r
111     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];\r
112 \r
113     while (1) {\r
114         /*\r
115          * Read one byte from the pipe.\r
116          */\r
117         ret = read(osxsel_pipe[0], &c, 1);\r
118 \r
119         if (ret <= 0)\r
120             return;                    /* terminate the thread */\r
121 \r
122         /*\r
123          * Now set up the select data.\r
124          */\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
129         n = osxsel_inmax;\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
134 \r
135         /*\r
136          * Perform the select.\r
137          */\r
138         ret = select(n, &r, &w, &x, NULL);\r
139 \r
140         /*\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
144          * not be!\r
145          */\r
146         if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r))\r
147             continue;                  /* just loop round again */\r
148 \r
149         /*\r
150          * Write the select results to shared data.\r
151          * \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
157          * \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
160          * anyway.\r
161          */\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
166         osxsel_outmax = n;\r
167         [osxsel_outlock unlock];\r
168 \r
169         /*\r
170          * Post a message to the main thread's message queue\r
171          * telling it that select data is available.\r
172          */\r
173         [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined\r
174                           location:NSMakePoint(0,0)\r
175                           modifierFlags:0\r
176                           timestamp:0\r
177                           windowNumber:0\r
178                           context:nil\r
179                           subtype:0\r
180                           data1:0\r
181                           data2:0]\r
182          atStart:NO];\r
183     }\r
184 \r
185     [pool release];\r
186 }\r
187 @end\r
188 \r
189 void osxsel_init(void)\r
190 {\r
191     uxsel_init();\r
192 \r
193     if (pipe(osxsel_pipe) < 0) {\r
194         fatalbox("Unable to set up inter-thread pipe for select");\r
195     }\r
196     [NSThread detachNewThreadSelector:@selector(runThread:)\r
197         toTarget:[[[OSXSel alloc] init] retain] withObject:nil];\r
198     /*\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
202      */\r
203     FD_ZERO(&osxsel_rfds_in);\r
204     FD_ZERO(&osxsel_wfds_in);\r
205     FD_ZERO(&osxsel_xfds_in);\r
206     osxsel_inmax = 0;\r
207     /*\r
208      * Initialise the mutex locks used to protect the data passed\r
209      * between threads.\r
210      */\r
211     osxsel_inlock = [[[NSLock alloc] init] retain];\r
212     osxsel_outlock = [[[NSLock alloc] init] retain];\r
213 }\r
214 \r
215 static void osxsel_start_select(void)\r
216 {\r
217     char c = 'g';                      /* for `Go!' :-) but it's never used */\r
218 \r
219     if (!inhibit_start_select)\r
220         write(osxsel_pipe[1], &c, 1);\r
221 }\r
222 \r
223 int uxsel_input_add(int fd, int rwx)\r
224 {\r
225     /*\r
226      * Add the new fd to the appropriate input fd_sets, then write\r
227      * to the inter-thread pipe.\r
228      */\r
229     [osxsel_inlock lock];\r
230     if (rwx & 1)\r
231         FD_SET(fd, &osxsel_rfds_in);\r
232     else\r
233         FD_CLR(fd, &osxsel_rfds_in);\r
234     if (rwx & 2)\r
235         FD_SET(fd, &osxsel_wfds_in);\r
236     else\r
237         FD_CLR(fd, &osxsel_wfds_in);\r
238     if (rwx & 4)\r
239         FD_SET(fd, &osxsel_xfds_in);\r
240     else\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
246 \r
247     /*\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
251      * itself.\r
252      */\r
253     return fd;\r
254 }\r
255 \r
256 void uxsel_input_remove(int id)\r
257 {\r
258     /*\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
262      */\r
263     uxsel_input_add(id, 0);\r
264 }\r
265 \r
266 /*\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
270  * \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
274  */\r
275 void osxsel_process_results(void)\r
276 {\r
277     int i;\r
278 \r
279     /*\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
286      */\r
287     inhibit_start_select = TRUE;\r
288 \r
289     [osxsel_outlock lock];\r
290 \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
294     }\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
298     }\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
302     }\r
303 \r
304     [osxsel_outlock unlock];\r
305 \r
306     inhibit_start_select = FALSE;\r
307     osxsel_start_select();\r
308 }\r