]>
Commit | Line | Data |
---|---|---|
1c1af145 | 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 | } |