This is EtermView.m in view mode; [Download] [Up]
/* The EtermView implementation. For legal stuff see the file COPYRIGHT. */ #import <dpsclient/dpsclient.h> #import <mach/mach.h> #import <libc.h> #import <stdio.h> #import <stdlib.h> #import <sys/ioctl.h> #import <sys/param.h> #import <sys/time.h> #import "EditListener.h" #import "EtermView.h" #import "EmacsApp.h" #import "etermSupport.h" #import "defaults.h" #import "display.h" extern char **environ; /* These are the window size limits imposed by GNUEmacs. */ #define MINWIDTH 10 #define MINHEIGHT 5 #define MAXWIDTH 300 #define MAXHEIGHT 300 /* Function prototypes for display.pswm */ void *new_display_rock (EtermView *theView); void input_from_emacs (int fd, void *theDisplay); /* Function prototypes for keycode.c */ void kc_init (void); int kc_keyforcode (int code, int flags); /* write(2), except it will keep retrying upon partial success. */ static int write_safely (int fd, char *buf, int n) { int i, start = 0; while (start < n) { i = write (fd, buf + start, n - start); if (i == -1) return i; /* Some error */ start += i; } return n; } /* Handle a connection to the event server socket */ static void connection_from_emacs (int fd, void *rock) { EtermView *view = (EtermView *) rock; int newfd = accept_server_connection (fd); if (newfd == -1) return; if ([view eventChannel]) close (newfd); else [view setEventChannel: fdopen (newfd, "w")]; } @implementation EtermView + initialize { static NXDefaultsVector etermDefaults = { {"NXAutoLaunch", "NO" }, {"NXFixedPitchFontSpacing", "0.95" }, {"HideOnAutoLaunch", "NO" }, {"EmacsPath", DEFAULT_EMACS_PATH}, {"LispPath", DEFAULT_LISP_PATH}, {NULL}, }; const char *sTypes[2], *rTypes[2]; sTypes[0] = NXAsciiPboardType; sTypes[1] = NULL; rTypes[0] = NXAsciiPboardType; rTypes[1] = NULL; NXRegisterDefaults ([NXApp appName], etermDefaults); [NXApp registerServicesMenuSendTypes: sTypes andReturnTypes: rTypes]; kc_init (); return self; } /* +initialize */ -initFrame: (const NXRect *) newFrame { self = [super initFrame: newFrame]; [self setFlipped: YES]; #if 0 [self notifyAncestorWhenFrameChanged: YES]; #endif spacing = atof (NXGetDefaultValue ([NXApp appName], "NXFixedPitchFontSpacing")); /* Check for sane values of spacing. */ if (spacing < 0.9) spacing = 0.9; else if (spacing > 2.0) spacing = 2.0; [self setFont: [self font]]; [[NXApp fontManager] setSelFont: [self font] isMultiple: NO]; return self; } /* -initFrame: */ -(float) spacing { return spacing; } -setSpacing: (float) newSpacing { char val[20]; spacing = newSpacing; [self setFont: [self font]]; sprintf (val, "%f", newSpacing); NXWriteDefault ([NXApp appName], "NXFixedPitchFontSpacing", val); return self; } -font { return [Font userFixedPitchFontOfSize: (float) 0 matrix: NX_FLIPPEDMATRIX]; } /* -font */ -setFont: newFont { NXCoord ascender, descender, lineHeight; NXRect frameRect, contentRect; Font *screenfont; int leading; /* NXTextFontInfo is broken for screen fonts, so call it before converting the font to a screenfont. */ NXTextFontInfo (newFont , &ascender, &descender, &lineHeight); screenfont = [newFont screenFont]; if (screenfont) newFont = screenfont; leading = (spacing - 1.0) * lineHeight; fontHeight = (int) (lineHeight * spacing + leading); fontWidth = (float) ([newFont getWidthOf : " "] - [newFont getWidthOf : " "] + 0.5); fontDescender = (int) (descender + leading); [newFont set]; if (screenfont) set_font (); else fix_font (fontWidth); displayFont = newFont; /* Snap the window size to a 1 x 1 character grid. */ [window getFrame: &frameRect]; [self windowWillResize: self toSize: &frameRect.size]; [Window getContentRect: &contentRect forFrameRect: &frameRect style: [window style]]; [window sizeWindow: contentRect.size.width : contentRect.size.height]; [self windowDidResize: self]; [self display]; return self; } /* -setFont: */ /* Sent by fontManager. */ -changeFont: sender { id selectedFont; /* Get the font selected in the FontPanel. */ selectedFont = [[NXApp fontManager] convertFont: [[NXApp fontManager] selFont]]; [self setFont: selectedFont]; [Font setUserFixedPitchFont: selectedFont]; return self; } /* -changeFont: */ -(FILE *) eventChannel; { return eventChannel; } /* -eventChannel */ -setEventChannel: (FILE *) fp; { eventChannel = fp; return self; } /* -setEventChannel: */ /* Constrain window so that the content view has an integral width in terms of characters. XXX this method needs some cleaning up to stop the window from flashing while resizing. */ -windowWillResize: sender toSize: (NXSize *) frameSize; { static int oldw = 0, oldh = 0; int neww, newh; NXRect frameRect, contentRect; int newwidth, newheight; int style = [window style]; NXSetRect (&frameRect, 0.0, 0.0, frameSize->width, frameSize->height); [Window getContentRect: &contentRect forFrameRect: &frameRect style: style]; newwidth = (int) (contentRect.size.width + fontWidth / 2 - BORDER_WIDTH * 2); newheight = (int) (contentRect.size.height + fontHeight / 2 - BORDER_WIDTH * 2); newwidth -= newwidth % fontWidth; newheight -= newheight % fontHeight; if (newwidth < MINWIDTH * fontWidth) newwidth = MINWIDTH * fontWidth; if (newwidth > MAXWIDTH * fontWidth) newwidth = MAXWIDTH * fontWidth; if (newheight < MINHEIGHT * fontHeight) newheight = MINHEIGHT * fontHeight; if (newheight > MAXHEIGHT * fontHeight) newheight = MAXHEIGHT * fontHeight; contentRect.size.width = (NXCoord) newwidth + BORDER_WIDTH * 2; contentRect.size.height = (NXCoord) newheight + BORDER_WIDTH * 2; [Window getFrameRect:&frameRect forContentRect:&contentRect style:style]; frameSize->width = frameRect.size.width; frameSize->height = frameRect.size.height; newh = newheight / fontHeight; neww = newwidth / fontWidth; if (newh != oldh || neww != oldw) { [self showTitle: newh : neww]; oldh = newh; oldw = neww; } return self; } /* -windowWillResize:toSize: */ /* Convert size to number of characters in content view. Inform child emacs process of our new size. */ -windowDidResize: sender { NXRect frameRect, contentRect; int style = [window style]; struct winsize winsize; [window getFrame: &frameRect]; [Window getContentRect: &contentRect forFrameRect: &frameRect style: style]; lines = (int) ((contentRect.size.height - BORDER_WIDTH * 2) / fontHeight); cols = (int) ((contentRect.size.width - BORDER_WIDTH * 2) / fontWidth); [self showTitle: lines : cols]; /* emacs doesn't do anything with a TIOCCSWINSZ if LINES nor COLS change, so send a redraw screen in that case. */ winsize.ws_row = lines; winsize.ws_col = cols; winsize.ws_xpixel = (int) contentRect.size.width; winsize.ws_ypixel = (int) contentRect.size.height; ioctl (masterChannel, TIOCSWINSZ, &winsize); if (eventChannel) fprintf (eventChannel, "(redraw-display)\n"); return self; } /* -windowDidResize: */ /* Start up the child emacs process, perhaps on a file. */ -startEmacs { int master, slave, ptynumber, portno, i, n; char **args, **env; char *path; const char *lisp; /* Grab a pty/tty pair */ create_channel (&master, &slave, &ptynumber); masterChannel = master; /* Create the server socket */ eventServerSocket = create_server_socket (&portno); DPSAddFD (eventServerSocket, connection_from_emacs, (void *) self, NX_RUNMODALTHRESHOLD); /* Frob the environment */ env = patch_env (environ, portno); /* Make sure the size is set correctly */ [self windowDidResize: self]; args = calloc (startNum + 6, sizeof (char *)); args[0] = "emacs"; path = malloc (MAXPATHLEN + 1); lisp = NXGetDefaultValue ([NXApp appName], "LispPath"); args[1] = "-l"; args[3] = "-f"; args[4] = "start-event-server"; n = 5; if (strrchr (lisp, '/')) args[2] = (char *)lisp; else { if ([[NXBundle mainBundle] getPath: path forResource: lisp ofType: NULL]) args[2] = path; else n = 1; } for (i=0 ; i<startNum ; i++) { args[n++] = startFiles[i]; } /* Fork off the Emacs */ fork_shell ((char *)NXGetDefaultValue ([NXApp appName], "EmacsPath"), args, env, slave); free (path); free (args); close (slave); rock = new_display_rock(self); DPSAddFD (master, input_from_emacs, rock, NX_RUNMODALTHRESHOLD); [window display]; return self; } /* -startEmacs: */ -(BOOL) acceptsFirstResponder { return YES; } /* -acceptsFirstResponder */ /* Keyboard input is given to the child emacs process. XXX this method needs some cleeaning up. */ -keyDown: (NXEvent *) theEvent; { NXEvent eventbuf; char buf[1024]; int i = 0; int code; if (theEvent->flags & NX_NUMERICPADMASK && theEvent->data.key.charCode >= 0xac && theEvent->data.key.charCode <= 0xaf) { /* Arrow keys (left, up, right, down) */ if (theEvent->flags & NX_SHIFTMASK) buf[i++] = "\302\326\306\026"[theEvent->data.key.charCode - 0xac]; else if (theEvent->flags & NX_CONTROLMASK) buf[i++] = "\001\274\005\276"[theEvent->data.key.charCode - 0xac]; else if (theEvent->flags & NX_ALTERNATEMASK) buf[i++] = "\202\220\206\216"[theEvent->data.key.charCode - 0xac]; else buf[i++] = "\002\020\006\016"[theEvent->data.key.charCode - 0xac]; } else if (theEvent->flags & NX_NUMERICPADMASK && theEvent->data.key.charCode == 0x03) { /* Enter key */ buf[i++] = '\r'; } else if (theEvent->flags & NX_ALTERNATEMASK) { /* Handle ALT key as a META key */ code = kc_keyforcode(theEvent->data.key.keyCode, theEvent->flags); if (code != -1) { buf[i++] = code | 0x80; } } else buf[i++] = theEvent->data.key.charCode; /* Grab as many keypressed events as we can from a modal event loop */ while (i < sizeof (buf) && (theEvent = [NXApp peekNextEvent: NX_ALLEVENTS into: &eventbuf]) && theEvent->type == NX_KEYDOWN && !(theEvent->flags & NX_COMMANDMASK)) { theEvent = [NXApp getNextEvent: NX_ALLEVENTS]; if (theEvent->flags & NX_NUMERICPADMASK && theEvent->data.key.charCode >= 0xac && theEvent->data.key.charCode <= 0xaf) { /* Arrow keys (left, up, right, down) */ if (theEvent->flags & NX_SHIFTMASK) buf[i++] = "\302\326\306\026"[theEvent->data.key.charCode - 0xac]; else if (theEvent->flags & NX_CONTROLMASK) buf[i++] = "\001\274\005\276"[theEvent->data.key.charCode - 0xac]; else if (theEvent->flags & NX_ALTERNATEMASK) buf[i++] = "\202\220\206\216"[theEvent->data.key.charCode - 0xac]; else buf[i++] = "\002\020\006\016"[theEvent->data.key.charCode - 0xac]; } else if (theEvent->flags & NX_NUMERICPADMASK && theEvent->data.key.charCode == 0x03) { /* Enter key */ buf[i++] = '\r'; } else if (theEvent->flags & NX_ALTERNATEMASK) { /* Handle ALT key as a META key */ code = kc_keyforcode (theEvent->data.key.keyCode, theEvent->flags); if (code != -1) buf[i++] = code | 0x80; } else buf[i++] = theEvent->data.key.charCode; } write_safely (masterChannel, buf, i); obscure_cursor (); return self; } /* -keyDown: */ /* Mouse input is converted to a character position and given to the child emacs process, but only if the emacs has opened an event port. */ -mouseEvent: (NXEvent *) theEvent : (int) button; { int x, y; char buf[35]; if (!eventChannel) return self; [self convertPoint: &theEvent->location fromView:nil]; x = (int) ((theEvent->location.x - BORDER_WIDTH) / fontWidth); y = (int) ((theEvent->location.y - BORDER_WIDTH) / fontHeight); if (x < 0) x = 0; if (y < 0) y = 0; if (x >= cols) x = cols - 1; if (y >= lines) y = lines - 1; if (oldx != x || oldy != y) { sprintf (buf, "\030%c(%d %d %d 1)\r", 0, (button + ((theEvent->flags & NX_SHIFTMASK) ? 8 : 0) + ((theEvent->flags & NX_CONTROLMASK) ? 16 : 0) + ((theEvent->flags & NX_ALTERNATEMASK) ? 32 : 0)), x, y); write_safely (masterChannel, buf, 2+strlen (buf+2)); oldx = x; oldy = y; } return self; } /* -mouseEvent:: */ -mouseDown: (NXEvent *) theEvent; { [self mouseEvent: theEvent: 1]; oldmask = [window addToEventMask:NX_LMOUSEDRAGGEDMASK]; return self; } /* -mouseDown: */ -mouseUp: (NXEvent *) theEvent; { oldmask = [window removeFromEventMask:NX_LMOUSEDRAGGEDMASK]; [self mouseEvent: theEvent: 1 + 128]; return self; } /* -mouseUp */ -mouseDragged: (NXEvent *) theEvent; { NXEvent *foundEvent = theEvent, *searchEvent; int filterCount = 0; while ((++filterCount < 10) && ((searchEvent = [NXApp getNextEvent:NX_LMOUSEDRAGGEDMASK waitFor:0.034 threshold:NX_MODALRESPTHRESHOLD]) != NULL)) foundEvent = searchEvent; [self mouseEvent: foundEvent: 1 + 128]; return self; } /* -mouseDragged */ -rightMouseDown: (NXEvent *) theEvent; { [self mouseEvent: theEvent: 4]; return self; } /* rightMouseDown: */ -rightMouseUp: (NXEvent *) theEvent; { [self mouseEvent: theEvent: 4 + 128]; return self; } /* rightMouseUp: */ -(BOOL) emacsOn { if (eventChannel) { if (startNum > 0) { int index; /* Now that we have a connection established, make sure all the open requests that have line numbers are pointed at the right place. */ for (index = 0 ; index < startNum ; index++) { if (startLines[index] >= 0) [self openFile:startFiles[index] onHost:"" atTrueLine:startLines[index]]; else [self newFile:startFiles[index]]; } startNum = 0; } return YES; } else return NO; } /* -emacsOn */ -(BOOL) newFile: (const char *) path { if (!eventChannel) return [self addFile:path atLine:-1]; fprintf (eventChannel, "(find-file \"%s\")\n", path); fflush (eventChannel); return YES; } /* -newFile: */ /* Quit, cut, copy, paste, and undo messages are sent to the child's event port. */ -quitEmacs { if (!eventChannel) return nil; fprintf (eventChannel, "(event-quit)\n"); fflush (eventChannel); return self; } /* -quitEmacs */ -pasteboard { if (!currentPasteboard) currentPasteboard = [self appPasteboard]; return currentPasteboard; } /* -pasteboard */ -appPasteboard { if (!mainPasteboard) mainPasteboard = [Pasteboard new]; return mainPasteboard; } /* -appPasteboard */ -setPasteboard: pboard { if (pboard != nil) currentPasteboard = pboard; return self; } /* -setPasteboard: */ -(BOOL) sendEmacsEvent: (char *) theEvent { if (!eventChannel) return NO; fprintf (eventChannel, "%s\n", theEvent); fflush (eventChannel); return YES; } /* -(BOOL) sendEmacsEvent: */ -pasteboardWritten { pasteboardWaiting = NO; return self; } -(BOOL)pasteboardWaiting { return pasteboardWaiting; } -undo: sender; { [self sendEmacsEvent: "(undo)"]; return self; } /* -undo: */ -save: sender; { [self sendEmacsEvent: "(save-buffer)"]; return self; } /* -save: */ -saveAll: sender; { [self sendEmacsEvent: "(save-some-buffers t)"]; return self; } /* -saveAll: */ -cut: sender; { [self cutTo: [self appPasteboard]]; return self; } /* -cut: */ -copy: sender; { [self copyTo: [self appPasteboard]]; return self; } /* -copy: */ -paste: sender; { [self pasteFrom: [self appPasteboard]]; return self; } /* -paste: */ -open: sender; { int i; char *path; const char *directory; const char *const *fileNames; if (!eventChannel) return self; if (!openPanel) openPanel = [OpenPanel new]; [openPanel allowMultipleFiles: YES]; [openPanel chooseDirectories: NO]; if ([openPanel runModal]) { directory = [openPanel directory]; fileNames = [openPanel filenames]; if (fileNames) { for (i = 0; fileNames[i]; i++) { path = malloc (2 + strlen (directory) + strlen (fileNames[i])); strcat (strcat (strcpy (path, directory), "/"), fileNames[i]); fprintf (eventChannel, "(find-file \"%s\")\n", path); fflush (eventChannel); free (path); } } } return self; } /* -open: */ -saveAs: sender; { const char *path; if (!eventChannel) return self; if (!savePanel) savePanel = [SavePanel new]; if ([savePanel runModal]) { path = [savePanel filename]; if (path) { fprintf (eventChannel, "(write-file \"%s\")\n", path); fflush (eventChannel); } } return self; } /* -saveAs: */ -(BOOL) cutTo: pasteboard { return [[self setPasteboard: pasteboard] sendEmacsEvent: "(event-cut)"]; } /* -(BOOL) cutTo: */ -(BOOL) copyTo: pasteboard { return [[self setPasteboard: pasteboard] sendEmacsEvent: "(event-copy)"]; } /* -(BOOL) copyTo: */ -(BOOL) pasteFrom: pasteboard { const NXAtom *types, *p; char *data; int len; if (!eventChannel || !pasteboard) return NO; types = [pasteboard types]; for (p = types; *p && strcmp (*p, NXAsciiPboardType); p++); if (*p && [pasteboard readType: NXAsciiPboardType data: &data length: &len] && len) { int c; char *tosend = data; fputs("(event-paste \"", eventChannel); /* Write out string, quoting quote, backslash, and newline */ for (tosend = data; len--; tosend++) { switch (c = *tosend) { case '\n': fputs ("\\n", eventChannel); break; case '\\': case '\"': putc('\\', eventChannel); /* FALL THROUGH */ default: putc (c, eventChannel); break; } } fputs ("\")\n", eventChannel); fflush (eventChannel); vm_deallocate (task_self (), (vm_address_t) data, len); } return YES; } /* -(BOOL) pasteFrom: */ -getDimensions: (int *) linesPtr : (int *) colsPtr { *linesPtr = lines; *colsPtr = cols; return self; } /* -getDimensions:: */ -(Font *) getDisplayFont: (int *) heightPtr : (int *) widthPtr : (int *) descenderPtr { *heightPtr = fontHeight; *widthPtr = fontWidth; *descenderPtr = fontDescender; return displayFont; } /* -getDisplayFont::: */ -showTitle: (int) newLines : (int) newColumns { char *newtitle; if (!titleprefix) return self; newtitle = malloc (22 + strlen (titleprefix)); sprintf (newtitle, "%s (%dx%d)", titleprefix, newColumns, newLines); [(Window *) window setTitle: newtitle]; free (newtitle); return self; } /* -showTitle:: */ -setTitle: (char *) title { titleprefix = title; [self showTitle: lines : cols]; return self; } /* -setTitle: */ -validRequestorForSendType: (NXAtom) typeSent andReturnType: (NXAtom) typeReturned { /* Check to make sure that the types are ones that we can handle. */ if (eventChannel && (typeSent == NXAsciiPboardType || typeSent == NULL) && (typeReturned == NXAsciiPboardType || typeReturned == NULL)) return self; /* Otherwise, return the default. */ return [super validRequestorForSendType: typeSent andReturnType: typeReturned]; } /* -validRequestorForSendType:andReturnType: */ -(BOOL) writeSelectionToPasteboard: pboard types: (NXAtom *) types { const NXAtom *p; BOOL rightType = NO; if (eventChannel && pboard && rock) { p = types; while (*p) { if (!strcmp(*p, NXAsciiPboardType)) { rightType = YES; break; } p++; } /* Ok, so this is slightly sick. We loop until emacs receives and processes the event. Then we know we've completely dealt with the pasteboard. We can't return until something has been pasted to it. Or the application will probably end up choking. We put in a timeout to prevent hanging. */ if (rightType) { struct timeval theTime; struct timezone dummy; long curTime, elapsedTime; gettimeofday(&theTime, &dummy); curTime = theTime.tv_sec + theTime.tv_usec / 1000000; elapsedTime = 0; pasteboardWaiting = YES; if (![self copyTo: pboard]) return NO; while (pasteboardWaiting && (elapsedTime < 5)) { input_from_emacs(masterChannel, rock); gettimeofday(&theTime, &dummy); elapsedTime = (theTime.tv_sec + theTime.tv_usec / 1000000) - curTime; } if (pasteboardWaiting) { NXBeep(); NXBeep(); return NO; } return YES; } } return NO; } /* -writeSelectionToPasteboard:types: */ -readSelectionFromPasteboard: pboard { [self sendEmacsEvent: "(call-interactively 'kill-region)"]; [self pasteFrom: pboard]; return self; } /* -readSelectionFromPasteboard: */ /* Opening up a file remotely and showing the right line number. */ -(int)openFile : (char *) fileName onHost : (char *) hostName atTrueLine : (int) line { if (!eventChannel) return [self addFile:fileName atLine:line]; fprintf(eventChannel, "(find-file \"%s\")\n", fileName); fprintf(eventChannel, "(goto-line %d)\n", line); fprintf(eventChannel, "(if (not overlay-arrow-position)"); fprintf(eventChannel, " (progn (setq overlay-arrow-position (point-marker)) "); fprintf(eventChannel, " (setq overlay-arrow-string \"=>\")))\n"); fprintf(eventChannel, "(set-marker overlay-arrow-position "); fprintf(eventChannel, " (- (point) (current-column)))\n"); fflush(eventChannel); return YES; } -(int)openFile : (char *) fileName onHost : (char *) hostName fromTrueLine : (int) line1 to : (int) line2 { return [self openFile:fileName onHost:hostName atTrueLine:line1]; } - (BOOL)addFile:(const char *)path atLine:(int)line { if (startFiles == NULL) { startFiles = (char **)malloc((startNum + 1) * sizeof(char *)); startLines = (int *)malloc((startNum + 1) * sizeof(int)); } else { startFiles = (char **)realloc(startFiles, (startNum+1) * sizeof (char *)); startLines = (int *)realloc(startLines, (startNum+1) * sizeof (int)); } if ((startFiles == NULL) || (startLines == NULL)) return NO; startLines[startNum] = line; startFiles[startNum] = strcpy (malloc (1 + strlen (path)), path); startNum++; return YES; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.