forked from Yasushi/putty
-
Notifications
You must be signed in to change notification settings - Fork 6
/
osxsel.m
308 lines (279 loc) · 8.71 KB
/
osxsel.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/*
* osxsel.m: OS X implementation of the front end interface to uxsel.
*/
#import <Cocoa/Cocoa.h>
#include <unistd.h>
#include "putty.h"
#include "osxclass.h"
/*
* The unofficial Cocoa FAQ at
*
* http://www.alastairs-place.net/cocoa/faq.txt
*
* says that Cocoa has the native ability to be given an fd and
* tell you when it becomes readable, but cannot tell you when it
* becomes _writable_. This is unacceptable to PuTTY, which depends
* for correct functioning on being told both. Therefore, I can't
* use the Cocoa native mechanism.
*
* Instead, I'm going to resort to threads. I start a second thread
* whose job is to do selects. At the termination of every select,
* it posts a Cocoa event into the main thread's event queue, so
* that the main thread gets select results interleaved with other
* GUI operations. Communication from the main thread _to_ the
* select thread is performed by writing to a pipe whose other end
* is one of the file descriptors being selected on. (This is the
* only sensible way, because we have to be able to interrupt a
* select in order to provide a new fd list.)
*/
/*
* In more detail, the select thread must:
*
* - start off by listening to _just_ the pipe, waiting to be told
* to begin a select.
*
* - when it receives the `start' command, it should read the
* shared uxsel data (which is protected by a mutex), set up its
* select, and begin it.
*
* - when the select terminates, it should write the results
* (perhaps minus the inter-thread pipe if it's there) into
* shared memory and dispatch a GUI event to let the main thread
* know.
*
* - the main thread will then think about it, do some processing,
* and _then_ send a command saying `now restart select'. Before
* sending that command it might easily have tinkered with the
* uxsel structures, which is why it waited before sending it.
*
* - EOF on the inter-thread pipe, of course, means the process
* has finished completely, so the select thread terminates.
*
* - The main thread may wish to adjust the uxsel settings in the
* middle of a select. In this situation it first writes the new
* data to the shared memory area, then notifies the select
* thread by writing to the inter-thread pipe.
*
* So the upshot is that the sequence of operations performed in
* the select thread must be:
*
* - read a byte from the pipe (which may block)
*
* - read the shared uxsel data and perform a select
*
* - notify the main thread of interesting select results (if any)
*
* - loop round again from the top.
*
* This is sufficient. Notifying the select thread asynchronously
* by writing to the pipe will cause its select to terminate and
* another to begin immediately without blocking. If the select
* thread's select terminates due to network data, its subsequent
* pipe read will block until the main thread is ready to let it
* loose again.
*/
static int osxsel_pipe[2];
static NSLock *osxsel_inlock;
static fd_set osxsel_rfds_in;
static fd_set osxsel_wfds_in;
static fd_set osxsel_xfds_in;
static int osxsel_inmax;
static NSLock *osxsel_outlock;
static fd_set osxsel_rfds_out;
static fd_set osxsel_wfds_out;
static fd_set osxsel_xfds_out;
static int osxsel_outmax;
static int inhibit_start_select;
/*
* NSThread requires an object method as its thread procedure, so
* here I define a trivial holding class.
*/
@class OSXSel;
@interface OSXSel : NSObject
{
}
- (void)runThread:(id)arg;
@end
@implementation OSXSel
- (void)runThread:(id)arg
{
char c;
fd_set r, w, x;
int n, ret;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
while (1) {
/*
* Read one byte from the pipe.
*/
ret = read(osxsel_pipe[0], &c, 1);
if (ret <= 0)
return; /* terminate the thread */
/*
* Now set up the select data.
*/
[osxsel_inlock lock];
memcpy(&r, &osxsel_rfds_in, sizeof(fd_set));
memcpy(&w, &osxsel_wfds_in, sizeof(fd_set));
memcpy(&x, &osxsel_xfds_in, sizeof(fd_set));
n = osxsel_inmax;
[osxsel_inlock unlock];
FD_SET(osxsel_pipe[0], &r);
if (n < osxsel_pipe[0]+1)
n = osxsel_pipe[0]+1;
/*
* Perform the select.
*/
ret = select(n, &r, &w, &x, NULL);
/*
* Detect the one special case in which the only
* interesting fd was the inter-thread pipe. In that
* situation only we are interested - the main thread will
* not be!
*/
if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r))
continue; /* just loop round again */
/*
* Write the select results to shared data.
*
* I _think_ we don't need this data to be lock-protected:
* it won't be read by the main thread until after we send
* a message indicating that we've finished writing it, and
* we won't start another select (hence potentially writing
* it again) until the main thread notifies us in return.
*
* However, I'm scared of multithreading and not totally
* convinced of my reasoning, so I'm going to lock it
* anyway.
*/
[osxsel_outlock lock];
memcpy(&osxsel_rfds_out, &r, sizeof(fd_set));
memcpy(&osxsel_wfds_out, &w, sizeof(fd_set));
memcpy(&osxsel_xfds_out, &x, sizeof(fd_set));
osxsel_outmax = n;
[osxsel_outlock unlock];
/*
* Post a message to the main thread's message queue
* telling it that select data is available.
*/
[NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
location:NSMakePoint(0,0)
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0]
atStart:NO];
}
[pool release];
}
@end
void osxsel_init(void)
{
uxsel_init();
if (pipe(osxsel_pipe) < 0) {
fatalbox("Unable to set up inter-thread pipe for select");
}
[NSThread detachNewThreadSelector:@selector(runThread:)
toTarget:[[[OSXSel alloc] init] retain] withObject:nil];
/*
* Also initialise (i.e. clear) the input fd_sets. Need not
* start a select just yet - the select thread will block until
* we have at least one fd for it!
*/
FD_ZERO(&osxsel_rfds_in);
FD_ZERO(&osxsel_wfds_in);
FD_ZERO(&osxsel_xfds_in);
osxsel_inmax = 0;
/*
* Initialise the mutex locks used to protect the data passed
* between threads.
*/
osxsel_inlock = [[[NSLock alloc] init] retain];
osxsel_outlock = [[[NSLock alloc] init] retain];
}
static void osxsel_start_select(void)
{
char c = 'g'; /* for `Go!' :-) but it's never used */
if (!inhibit_start_select)
write(osxsel_pipe[1], &c, 1);
}
int uxsel_input_add(int fd, int rwx)
{
/*
* Add the new fd to the appropriate input fd_sets, then write
* to the inter-thread pipe.
*/
[osxsel_inlock lock];
if (rwx & 1)
FD_SET(fd, &osxsel_rfds_in);
else
FD_CLR(fd, &osxsel_rfds_in);
if (rwx & 2)
FD_SET(fd, &osxsel_wfds_in);
else
FD_CLR(fd, &osxsel_wfds_in);
if (rwx & 4)
FD_SET(fd, &osxsel_xfds_in);
else
FD_CLR(fd, &osxsel_xfds_in);
if (osxsel_inmax < fd+1)
osxsel_inmax = fd+1;
[osxsel_inlock unlock];
osxsel_start_select();
/*
* We must return an `id' which will be passed back to us at
* the time of uxsel_input_remove. Since we have no need to
* store ids in that sense, we might as well go with the fd
* itself.
*/
return fd;
}
void uxsel_input_remove(int id)
{
/*
* Remove the fd from all the input fd_sets. In this
* implementation, the simplest way to do that is to call
* uxsel_input_add with rwx==0!
*/
uxsel_input_add(id, 0);
}
/*
* Function called in the main thread to process results. It will
* have to read the output fd_sets, go through them, call back to
* uxsel with the results, and then write to the inter-thread pipe.
*
* This function will have to be called from an event handler in
* osxmain.m, which will therefore necessarily contain a small part
* of this mechanism (along with calling osxsel_init).
*/
void osxsel_process_results(void)
{
int i;
/*
* We must write to the pipe to start a fresh select _even if_
* there were no changes. So for efficiency, we set a flag here
* which inhibits uxsel_input_{add,remove} from writing to the
* pipe; then once we finish processing, we clear the flag
* again and write a single byte ourselves. It's cleaner,
* because it wakes up the select thread fewer times.
*/
inhibit_start_select = TRUE;
[osxsel_outlock lock];
for (i = 0; i < osxsel_outmax; i++) {
if (FD_ISSET(i, &osxsel_xfds_out))
select_result(i, 4);
}
for (i = 0; i < osxsel_outmax; i++) {
if (FD_ISSET(i, &osxsel_rfds_out))
select_result(i, 1);
}
for (i = 0; i < osxsel_outmax; i++) {
if (FD_ISSET(i, &osxsel_wfds_out))
select_result(i, 2);
}
[osxsel_outlock unlock];
inhibit_start_select = FALSE;
osxsel_start_select();
}