зеркало из https://github.com/github/putty.git
309 строки
8.7 KiB
Objective-C
309 строки
8.7 KiB
Objective-C
/*
|
|
* 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();
|
|
}
|