| 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 | } |