1/* 2copyright 2002-2003 Alexander Malmberg <alexander@malmberg.org> 3 2005-2016 Riccardo Mottola <rmottola@users.sf.net> 4 5This file is a part of Terminal.app. Terminal.app is free software; you 6can redistribute it and/or modify it under the terms of the GNU General 7Public License as published by the Free Software Foundation; version 2 8of the License. See COPYING or main.m for more information. 9*/ 10 11/* 12TODO: Move pty and child process handling to another class. Make this a 13stupid but fast character cell display view. 14*/ 15 16/* define this if you need the forkpty replacement and it is not automatically 17activated */ 18#undef USE_FORKPTY_REPLACEMENT 19 20 21/* check for solaris */ 22#if defined (__SVR4) && defined (__sun) 23#define __SOLARIS__ 1 24#define USE_FORKPTY_REPLACEMENT 1 25#endif 26 27#include <math.h> 28#include <unistd.h> 29 30#ifdef __NetBSD__ 31# include <sys/types.h> 32# include <sys/ioctl.h> 33# include <termios.h> 34# include <pcap.h> 35# include <util.h> 36#define TCSETS TIOCSETA 37#elif defined(__FreeBSD__) || defined(__DragonFly__) 38# include <sys/types.h> 39# include <sys/ioctl.h> 40# include <termios.h> 41# include <libutil.h> 42# include <pcap.h> 43#elif defined(__OpenBSD__) 44# include <termios.h> 45# include <util.h> 46# include <sys/ioctl.h> 47#elif defined (__GNU__) 48#else 49# include <termio.h> 50#endif 51 52#include <sys/time.h> 53#include <sys/types.h> 54#include <unistd.h> 55#include <fcntl.h> 56#if !defined __FreeBSD__ && !defined __DragonFly__ 57#if !(defined (__NetBSD__)) && !(defined (__SOLARIS__)) && !(defined(__OpenBSD__)) 58# include <pty.h> 59#endif 60#endif 61 62#import <Foundation/NSBundle.h> 63#import <Foundation/NSDebug.h> 64#import <Foundation/NSNotification.h> 65#import <Foundation/NSRunLoop.h> 66#import <Foundation/NSUserDefaults.h> 67#import <Foundation/NSCharacterSet.h> 68#import <Foundation/NSArchiver.h> 69#import <GNUstepBase/Unicode.h> 70#import <AppKit/NSApplication.h> 71#import <AppKit/NSPasteboard.h> 72#import <AppKit/NSDragging.h> 73#import <AppKit/NSEvent.h> 74#import <AppKit/NSGraphics.h> 75#import <AppKit/NSScroller.h> 76#import <AppKit/DPSOperators.h> 77 78#import "TerminalView.h" 79 80#import "TerminalViewPrefs.h" 81#import "TerminalParser_Linux.h" 82 83 84/* forkpty replacement */ 85#ifdef USE_FORKPTY_REPLACEMENT 86#include <stdio.h> /* for stderr and perror*/ 87#include <errno.h> /* for int errno */ 88#include <fcntl.h> 89#include <sys/termios.h> 90#include <stropts.h> 91#include <stdlib.h> 92#include <string.h> 93 94#define PATH_TTY "/dev/tty" 95 96int ptyMakeControllingTty(int *slaveFd, const char *slaveName) 97{ 98 pid_t pgid; 99 int fd; 100 101 if (!slaveFd || *slaveFd < 0) 102 { 103 perror("slaveFd invalid"); 104 return -1; 105 } 106 107 /* disconnect from the old controlling tty */ 108#ifdef TIOCNOTTY 109 if ((fd = open(PATH_TTY, O_RDWR | O_NOCTTY)) >= 0 ) 110 { 111 ioctl(fd, TIOCNOTTY, NULL); 112 close(fd); 113 } 114#endif 115 116 117 pgid = setsid(); /* create session and set process ID */ 118 if (pgid == -1) 119 { 120 if (errno == EPERM) 121 perror("EPERM error on setsid"); 122 } 123 124 /* Make it our controlling tty */ 125#ifdef TIOCSCTTY 126 if (ioctl(*slaveFd, TIOCSCTTY, NULL) == -1) 127 return -1; 128#else 129#warning TIOCSCTTY replacement 130 { 131 /* first terminal we open after setsid() is the controlling one */ 132 char *controllingTty; 133 int ctr_fdes; 134 135 controllingTty = ttyname(*slaveFd); 136 ctr_fdes = open(controllingTty, O_RDWR); 137 close(ctr_fdes); 138 } 139#endif /* TIOCSCTTY */ 140 141#if defined (TIOCSPGRP) 142 ioctl (0, TIOCSPGRP, &pgid); 143#else 144#warning no TIOCSPGRP 145 tcsetpgrp (0, pgid); 146#endif 147 148 149 150 if ((fd = open(slaveName, O_RDWR)) >= 0) 151 { 152 close(*slaveFd); 153 *slaveFd = fd; 154 printf("Got new filedescriptor...\n"); 155 } 156 if ((fd = open(PATH_TTY, O_RDWR)) == -1) 157 return -1; 158 159 close(fd); 160 161 return 0; 162} 163 164int openpty(int *amaster, int *aslave, char *name, const struct termios *termp, const struct winsize *winp) 165{ 166 int fdm, fds; 167 char *slaveName; 168 169 fdm = open("/dev/ptmx", O_RDWR); /* open master */ 170 if (fdm == -1) 171 { 172 perror("openpty:open(master)"); 173 return -1; 174 } 175 if(grantpt(fdm)) /* grant access to the slave */ 176 { 177 perror("openpty:grantpt(master)"); 178 close(fdm); 179 return -1; 180 } 181 if(unlockpt(fdm)) /* unlock the slave terminal */ 182 { 183 perror("openpty:unlockpt(master)"); 184 close(fdm); 185 return -1; 186 } 187 188 slaveName = ptsname(fdm); /* get name of the slave */ 189 if (slaveName == NULL) 190 { 191 perror("openpty:ptsname(master)"); 192 close(fdm); 193 return -1; 194 } 195 if (name) /* of name ptr not null, copy it name back */ 196 strcpy(name, slaveName); 197 198 fds = open(slaveName, O_RDWR | O_NOCTTY); /* open slave */ 199 if (fds == -1) 200 { 201 perror("openpty:open(slave)"); 202 close (fdm); 203 return -1; 204 } 205 206 /* ldterm and ttcompat are automatically pushed on the stack on some systems*/ 207#ifdef __SOLARIS__ 208 if (ioctl(fds, I_PUSH, "ptem") == -1) /* pseudo terminal module */ 209 { 210 perror("openpty:ioctl(I_PUSH, ptem"); 211 close(fdm); 212 close(fds); 213 return -1; 214 } 215 if (ioctl(fds, I_PUSH, "ldterm") == -1) /* ldterm must stay atop ptem */ 216 { 217 perror("forkpty:ioctl(I_PUSH, ldterm"); 218 close(fdm); 219 close(fds); 220 return -1; 221 } 222#endif 223 224 /* set terminal parameters if present */ 225 if (termp) 226 ioctl(fds, TCSETS, termp); 227 if (winp) 228 ioctl(fds, TIOCSWINSZ, winp); 229 230 *amaster = fdm; 231 *aslave = fds; 232 return 0; 233} 234 235int forkpty (int *amaster, char *slaveName, const struct termios *termp, const struct winsize *winp) 236{ 237 int fdm, fds; /* master and slave file descriptors */ 238 pid_t pid; 239 240 if (openpty(&fdm, &fds, slaveName, termp, winp) == -1) 241 { 242 perror("forkpty:openpty()"); 243 return -1; 244 } 245 246 247 pid = fork(); 248 if (pid == -1) 249 { 250 /* error */ 251 perror("forkpty:fork()"); 252 close(fdm); 253 close(fds); 254 return -1; 255 } else if (pid == 0) 256 { 257 /* child */ 258 ptyMakeControllingTty(&fds, slaveName); 259 if (fds != STDIN_FILENO && dup2(fds, STDIN_FILENO) == -1) 260 perror("error duplicationg stdin"); 261 if (fds != STDOUT_FILENO && dup2(fds, STDOUT_FILENO) == -1) 262 perror("error duplicationg stdout"); 263 if (fds != STDERR_FILENO && dup2(fds, STDERR_FILENO) == -1) 264 perror("error duplicationg stderr"); 265 266 if (fds != STDIN_FILENO && fds != STDOUT_FILENO && fds != STDERR_FILENO) 267 close(fds); 268 269 270 close (fdm); 271 } else 272 { 273 /* father */ 274 close (fds); 275 *amaster = fdm; 276 } 277 return pid; 278} 279 280#endif /* forpkty replacement */ 281 282/* TODO */ 283@interface NSView (unlockfocus) 284-(void) unlockFocusNeedsFlush: (BOOL)flush; 285@end 286 287 288NSString 289 *TerminalViewBecameIdleNotification=@"TerminalViewBecameIdle", 290 *TerminalViewBecameNonIdleNotification=@"TerminalViewBecameNonIdle", 291 292 *TerminalViewTitleDidChangeNotification=@"TerminalViewTitleDidChange"; 293 294 295 296@interface TerminalView (scrolling) 297-(void) _updateScroller; 298-(void) _scrollTo: (int)new_scroll update: (BOOL)update; 299-(void) setScroller: (NSScroller *)sc; 300@end 301 302@interface TerminalView (selection) 303-(void) _clearSelection; 304@end 305 306@interface TerminalView (input) <RunLoopEvents> 307-(void) closeProgram; 308-(void) runShell; 309-(void) runProgram: (NSString *)path 310 withArguments: (NSArray *)args 311 initialInput: (NSString *)d; 312@end 313 314 315/** 316TerminalScreen protocol implementation and rendering methods 317**/ 318 319@implementation TerminalView (display) 320 321#define ADD_DIRTY(ax0,ay0,asx,asy) do { \ 322 if (dirty.x0==-1) \ 323 { \ 324 dirty.x0=(ax0); \ 325 dirty.y0=(ay0); \ 326 dirty.x1=(ax0)+(asx); \ 327 dirty.y1=(ay0)+(asy); \ 328 } \ 329 else \ 330 { \ 331 if (dirty.x0>(ax0)) dirty.x0=(ax0); \ 332 if (dirty.y0>(ay0)) dirty.y0=(ay0); \ 333 if (dirty.x1<(ax0)+(asx)) dirty.x1=(ax0)+(asx); \ 334 if (dirty.y1<(ay0)+(asy)) dirty.y1=(ay0)+(asy); \ 335 } \ 336 } while (0) 337 338 339#define SCREEN(x,y) (screen[(y)*sx+(x)]) 340 341 342/* handle accumulated pending scrolls with a single composite */ 343-(void) _handlePendingScroll: (BOOL)lockFocus 344{ 345 float x0,y0,w,h,dx,dy; 346 347 if (!pending_scroll) 348 return; 349 350 if (pending_scroll>=sy || pending_scroll<=-sy) 351 { 352 pending_scroll=0; 353 return; 354 } 355 356 NSDebugLLog(@"draw",@"_handlePendingScroll %i %i",pending_scroll,lockFocus); 357 358 dx=x0=0; 359 w=fx*sx; 360 361 if (pending_scroll>0) 362 { 363 y0=0; 364 h=(sy-pending_scroll)*fy; 365 dy=pending_scroll*fy; 366 y0=sy*fy-y0-h; 367 dy=sy*fy-dy-h; 368 } 369 else 370 { 371 pending_scroll=-pending_scroll; 372 373 y0=pending_scroll*fy; 374 h=(sy-pending_scroll)*fy; 375 dy=0; 376 y0=sy*fy-y0-h; 377 dy=sy*fy-dy-h; 378 } 379 380 if (lockFocus) 381 [self lockFocus]; 382 DPScomposite(GSCurrentContext(),border_x+x0,border_y+y0,w,h, 383 [self gState],border_x+dx,border_y+dy,NSCompositeCopy); 384 if (lockFocus) 385 [self unlockFocusNeedsFlush: NO]; 386 387 num_scrolls++; 388 pending_scroll=0; 389} 390 391 392static int total_draw=0; 393 394 395static const float col_h[8]={ 0,240,120,180, 0,300, 60, 0}; 396static const float col_s[8]={0.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0}; 397 398static void set_background(NSGraphicsContext *gc, 399 unsigned char color,unsigned char in) 400{ 401 float bh,bs,bb; 402 int bg=color>>4; 403 404 if (bg==0) 405 bb=0.0; 406 else if (bg>=8) 407 bg-=8,bb=1.0; 408 else 409 bb=0.6; 410 bs=col_s[bg]; 411 bh=col_h[bg]/360.0; 412 413 DPSsethsbcolor(gc,bh,bs,bb); 414} 415 416static void set_foreground(NSGraphicsContext *gc, 417 unsigned char color,unsigned char in, BOOL blackOnWhite) 418{ 419 int fg=color; 420 float h,s,b; 421 422if (blackOnWhite) 423 { 424 if (color == 0) { fg = 7; in = 2; } // Black becomes white 425 else if (color == 7) { fg = 0; in = 0; } // White becomes black 426 //else in = 3; // Other colors are saturated 427 } 428 429 if (fg>=8) 430 { 431 in++; 432 fg-=8; 433 } 434 435 if (fg==0) 436 { 437 if (in==2) 438 b=0.4; 439 else 440 b=0.0; 441 } 442 else if (in==0) 443 b=0.6; 444 else if (in==1) 445 b=0.8; 446 else 447 b=1.0; 448 449 h=col_h[fg]/360.0; 450 s=col_s[fg]; 451 if (in==2) 452 s*=0.75; 453 454 DPSsethsbcolor(gc,h,s,b); 455} 456 457 458-(void) drawRect: (NSRect)r 459{ 460 int ix,iy; 461 char buf[8]; 462 NSGraphicsContext *cur=GSCurrentContext(); 463 int x0,y0,x1,y1; 464 NSFont *f,*current_font=nil; 465 466 int encoding; 467 468 469 NSDebugLLog(@"draw",@"drawRect: (%g %g)+(%g %g) %i\n", 470 r.origin.x,r.origin.y,r.size.width,r.size.height, 471 draw_all); 472 473 if (pending_scroll) 474 [self _handlePendingScroll: NO]; 475 476 /* draw the border around the view if needed */ 477 { 478 float a,b; 479 if (blackOnWhite) 480 DPSsetgray(cur,1.0); 481 else 482 DPSsetgray(cur,0.0); 483 if (r.origin.x<border_x) 484 DPSrectfill(cur,r.origin.x,r.origin.y,border_x-r.origin.x,r.size.height); 485 if (r.origin.y<border_y) 486 DPSrectfill(cur,r.origin.x,r.origin.y,r.size.width,border_y-r.origin.y); 487 488 a=border_x+sx*fx; 489 b=r.origin.x+r.size.width; 490 if (b>a) 491 DPSrectfill(cur,a,r.origin.y,b-a,r.size.height); 492 a=border_y+sy*fy; 493 b=r.origin.y+r.size.height; 494 if (b>a) 495 DPSrectfill(cur,r.origin.x,a,r.size.width,b-a); 496 } 497 498 /* figure out what character cells might need redrawing */ 499 r.origin.x-=border_x; 500 r.origin.y-=border_y; 501 502 x0=floor(r.origin.x/fx); 503 x1=ceil((r.origin.x+r.size.width)/fx); 504 if (x0<0) x0=0; 505 if (x1>=sx) x1=sx; 506 507 y1=floor(r.origin.y/fy); 508 y0=ceil((r.origin.y+r.size.height)/fy); 509 y0=sy-y0; 510 y1=sy-y1; 511 if (y0<0) y0=0; 512 if (y1>=sy) y1=sy; 513 514 NSDebugLLog(@"draw",@"dirty (%i %i)-(%i %i)\n",x0,y0,x1,y1); 515 516 draw_cursor=draw_cursor || draw_all || 517 (SCREEN(cursor_x,cursor_y).attr&0x80)!=0; 518 519 { 520 int ry; 521 screen_char_t *ch; 522 float scr_y,scr_x,start_x; 523 524 /* setting the color is slow, so we try to avoid it */ 525 unsigned char l_color,l_attr,color; 526 527 /* Fill the background of dirty cells. Since the background doesn't 528 change that often, runs of dirty cells with the same background color 529 are combined and drawn with a single rectfill. */ 530 l_color=0; 531 l_attr=0; 532 set_foreground(cur,l_color,l_attr,blackOnWhite); 533 for (iy=y0;iy<y1;iy++) 534 { 535 ry=iy+current_scroll; 536 if (ry>=0) 537 ch=&SCREEN(x0,ry); 538 else 539 ch=&sbuf[x0+(max_scrollback+ry)*sx]; 540 541 scr_y=(sy-1-iy)*fy+border_y; 542/* 543#define R(scr_x,scr_y,fx,fy) \ 544 DPSgsave(cur); \ 545 DPSsetgray(cur,0.0); \ 546 DPSrectfill(cur,scr_x,scr_y,fx,fy); \ 547 DPSgrestore(cur); \ 548 DPSrectstroke(cur,scr_x,scr_y,fx,fy); \ 549*/ 550 551/* ~400 cycles/cell on average */ 552#define R(scr_x,scr_y,fx,fy) DPSrectfill(cur,scr_x,scr_y,fx,fy) 553 start_x=-1; 554 for (ix=x0;ix<x1;ix++,ch++) 555 { 556 if (!draw_all && !(ch->attr&0x80)) 557 { 558 if (start_x!=-1) 559 { 560 scr_x=ix*fx+border_x; 561 R(start_x,scr_y,scr_x-start_x,fy); 562 start_x=-1; 563 } 564 continue; 565 } 566 567 scr_x=ix*fx+border_x; 568 569 if (ch->attr&0x8) 570 { 571 color=ch->color&0xf; 572 if (ch->attr&0x40) color^=0xf; 573 if (color!=l_color || (ch->attr&0x03)!=l_attr) 574 { 575 if (start_x!=-1) 576 { 577 R(start_x,scr_y,scr_x-start_x,fy); 578 start_x=scr_x; 579 } 580 581 l_color=color; 582 l_attr=ch->attr&0x03; 583 set_foreground(cur,l_color,l_attr,blackOnWhite); 584 } 585 } 586 else 587 { 588 color=ch->color&0xf0; 589 if (ch->attr&0x40) color^=0xf0; 590 if (color!=l_color) 591 { 592 if (start_x!=-1) 593 { 594 R(start_x,scr_y,scr_x-start_x,fy); 595 start_x=scr_x; 596 } 597 598 l_color=color; 599 l_attr=ch->attr&0x03; 600 set_background(cur,l_color,l_attr); 601 } 602 } 603 604 if (start_x==-1) 605 start_x=scr_x; 606 } 607 608 if (start_x!=-1) 609 { 610 scr_x=ix*fx+border_x; 611 R(start_x,scr_y,scr_x-start_x,fy); 612 } 613 } 614 615 /* now draw any dirty characters */ 616 for (iy=y0;iy<y1;iy++) 617 { 618 ry=iy+current_scroll; 619 if (ry>=0) 620 ch=&SCREEN(x0,ry); 621 else 622 ch=&sbuf[x0+(max_scrollback+ry)*sx]; 623 624 scr_y=(sy-1-iy)*fy+border_y; 625 626 for (ix=x0;ix<x1;ix++,ch++) 627 { 628 if (!draw_all && !(ch->attr&0x80)) 629 continue; 630 631 ch->attr&=0x7f; 632 633 scr_x=ix*fx+border_x; 634 635 /* ~1700 cycles/change */ 636 if (ch->attr&0x02 || (ch->ch!=0 && ch->ch!=32)) 637 { 638 if (!(ch->attr&0x8)) 639 { 640 color=ch->color&0xf; 641 if (ch->attr&0x40) color^=0xf; 642 if (color!=l_color || (ch->attr&0x03)!=l_attr) 643 { 644 l_color=color; 645 l_attr=ch->attr&0x03; 646 set_foreground(cur,l_color,l_attr,blackOnWhite); 647 } 648 } 649 else 650 { 651 color=ch->color&0xf0; 652 if (ch->attr&0x40) color^=0xf0; 653 if (color!=l_color) 654 { 655 l_color=color; 656 l_attr=ch->attr&0x03; 657 set_background(cur,l_color,l_attr); 658 } 659 } 660 } 661 662 if (ch->ch!=0 && ch->ch!=32 && ch->ch!=MULTI_CELL_GLYPH) 663 { 664 total_draw++; 665 if ((ch->attr&3)==2) 666 { 667 encoding=boldFont_encoding; 668 f=boldFont; 669 } 670 else 671 { 672 encoding=font_encoding; 673 f=font; 674 } 675 if (f!=current_font) 676 { 677 /* ~190 cycles/change */ 678 [f set]; 679 current_font=f; 680 } 681 682 /* we short-circuit utf8 for performance with back-art */ 683 /* TODO: short-circuit latin1 too? */ 684 if (encoding==NSUTF8StringEncoding) 685 { 686 unichar uch=ch->ch; 687 if (uch>=0x800) 688 { 689 buf[2]=(uch&0x3f)|0x80; 690 uch>>=6; 691 buf[1]=(uch&0x3f)|0x80; 692 uch>>=6; 693 buf[0]=(uch&0x0f)|0xe0; 694 buf[3]=0; 695 } 696 else if (uch>=0x80) 697 { 698 buf[1]=(uch&0x3f)|0x80; 699 uch>>=6; 700 buf[0]=(uch&0x1f)|0xc0; 701 buf[2]=0; 702 } 703 else 704 { 705 buf[0]=uch; 706 buf[1]=0; 707 } 708 } 709 else 710 { 711 unichar uch=ch->ch; 712 if (uch<=0x80) 713 { 714 buf[0]=uch; 715 buf[1]=0; 716 } 717 else 718 { 719 unsigned char *pbuf=(unsigned char *)buf; 720 unsigned int dlen=sizeof(buf)-1; 721 GSFromUnicode(&pbuf,&dlen,&uch,1,encoding,NULL,GSUniTerminate); 722 } 723 } 724 /* ~580 cycles */ 725 DPSmoveto(cur,scr_x+fx0,scr_y+fy0); 726 /* baseline here for mc-case 0.65 */ 727 /* ~3800 cycles */ 728 DPSshow(cur,buf); 729 730 /* ~95 cycles to ARTGState -DPSshow:... */ 731 /* ~343 cycles to isEmpty */ 732 /* ~593 cycles to currentpoint */ 733 /* ~688 cycles to transform */ 734 /* ~1152 cycles to FTFont -drawString:... */ 735 /* ~1375 cycles to -drawString:... setup */ 736 /* ~1968 cycles cmap lookup */ 737 /* ~2718 cycles sbit lookup */ 738 /* ~~2750 cycles blit setup */ 739 /* ~3140 cycles blit loop, empty call */ 740 /* ~3140 cycles blit loop, setup */ 741 /* ~3325 cycles blit loop, no write */ 742 /* ~3800 cycles total */ 743 } 744 745 /* underline */ 746 if (ch->attr&0x4) 747 DPSrectfill(cur,scr_x,scr_y,fx,1); 748 } 749 } 750 } 751 752 if (draw_cursor) 753 { 754 float x,y; 755 [[TerminalViewDisplayPrefs cursorColor] set]; 756 757 x=cursor_x*fx+border_x; 758 y=(sy-1-cursor_y+current_scroll)*fy+border_y; 759 760 switch ([TerminalViewDisplayPrefs cursorStyle]) 761 { 762 case CURSOR_LINE: 763 DPSrectfill(cur,x,y,fx,fy*0.1); 764 break; 765 case CURSOR_BLOCK_STROKE: 766 DPSrectstroke(cur,x+0.5,y+0.5,fx-1.0,fy-1.0); 767 break; 768 case CURSOR_BLOCK_FILL: 769 DPSrectfill(cur,x,y,fx,fy); 770 break; 771 case CURSOR_BLOCK_INVERT: 772 DPScompositerect(cur,x,y,fx,fy, 773 NSCompositeHighlight); 774 break; 775 } 776 draw_cursor=NO; 777 } 778 779 NSDebugLLog(@"draw",@"total_draw=%i",total_draw); 780 781 draw_all=1; 782} 783 784-(BOOL) isOpaque 785{ 786 return YES; 787} 788 789-(void) setNeedsDisplayInRect: (NSRect)r 790{ 791 draw_all=2; 792 [super setNeedsDisplayInRect: r]; 793} 794 795-(void) setNeedsLazyDisplayInRect: (NSRect)r 796{ 797 if (draw_all==1) 798 draw_all=0; 799 [super setNeedsDisplayInRect: r]; 800} 801 802 803-(void) benchmark: (id)sender 804{ 805 int i; 806 double t1,t2; 807 NSRect r=[self frame]; 808 t1=[NSDate timeIntervalSinceReferenceDate]; 809 total_draw=0; 810 for (i=0;i<100;i++) 811 { 812 draw_all=2; 813 [self lockFocus]; 814 [self drawRect: r]; 815 [self unlockFocusNeedsFlush: NO]; 816 } 817 t2=[NSDate timeIntervalSinceReferenceDate]; 818 t2-=t1; 819 fprintf(stderr,"%8.4f %8.5f/redraw total_draw=%i\n",t2,t2/i,total_draw); 820} 821 822 823-(void) ts_setTitle: (NSString *)new_title type: (int)title_type 824{ 825 NSDebugLLog(@"ts",@"setTitle: %@ type: %i",new_title,title_type); 826 if (title_type==1 || title_type==0) 827 ASSIGN(title_miniwindow,new_title); 828 if (title_type==2 || title_type==0) 829 ASSIGN(title_window,new_title); 830 if (title_type==3) 831 ASSIGN(title_filename,new_title); 832 [[NSNotificationCenter defaultCenter] 833 postNotificationName: TerminalViewTitleDidChangeNotification 834 object: self]; 835} 836 837 838-(void) ts_goto: (int)x :(int)y 839{ 840 NSDebugLLog(@"ts",@"goto: %i:%i",x,y); 841 cursor_x=x; 842 cursor_y=y; 843 if (cursor_x>=sx) cursor_x=sx-1; 844 if (cursor_x<0) cursor_x=0; 845 if (cursor_y>=sy) cursor_y=sy-1; 846 if (cursor_y<0) cursor_y=0; 847} 848 849-(void) ts_putChar: (screen_char_t)ch count: (int)c at: (int)x :(int)y 850{ 851 int i; 852 screen_char_t *s; 853 854 NSDebugLLog(@"ts",@"putChar: '%c' %02x %02x count: %i at: %i:%i", 855 ch.ch,ch.color,ch.attr,c,x,y); 856 857 if (y<0 || y>=sy) return; 858 if (x+c>sx) 859 c=sx-x; 860 if (x<0) 861 { 862 c-=x; 863 x=0; 864 } 865 s=&SCREEN(x,y); 866 ch.attr|=0x80; 867 for (i=0;i<c;i++) 868 *s++=ch; 869 ADD_DIRTY(x,y,c,1); 870} 871 872-(void) ts_putChar: (screen_char_t)ch count: (int)c offset: (int)ofs 873{ 874 int i; 875 screen_char_t *s; 876 877 NSDebugLLog(@"ts",@"putChar: '%c' %02x %02x count: %i offset: %i", 878 ch.ch,ch.color,ch.attr,c,ofs); 879 880 if (ofs+c>sx*sy) 881 c=sx*sy-ofs; 882 if (ofs<0) 883 { 884 c-=ofs; 885 ofs=0; 886 } 887 s=&SCREEN(ofs,0); 888 ch.attr|=0x80; 889 for (i=0;i<c;i++) 890 *s++=ch; 891 ADD_DIRTY(0,0,sx,sy); /* TODO */ 892} 893 894-(void) ts_scrollUp: (int)t :(int)b rows: (int)nr save: (BOOL)save 895{ 896 screen_char_t *d, *s; 897 898 NSDebugLLog(@"ts",@"scrollUp: %i:%i rows: %i save: %i", 899 t,b,nr,save); 900 901 if (save && t==0 && b==sy) /* TODO? */ 902 { 903 int num; 904 if (nr<max_scrollback) 905 { 906 memmove(sbuf,&sbuf[sx*nr],sizeof(screen_char_t)*sx*(max_scrollback-nr)); 907 num=nr; 908 } 909 else 910 num=max_scrollback; 911 912 if (num<sy) 913 { 914 memmove(&sbuf[sx*(max_scrollback-num)],screen,num*sx*sizeof(screen_char_t)); 915 } 916 else 917 { 918 memmove(&sbuf[sx*(max_scrollback-num)],screen,sy*sx*sizeof(screen_char_t)); 919 920 /* TODO: should this use video_erase_char? */ 921 memset(&sbuf[sx*(max_scrollback-num+sy)],0,sx*(num-sy)*sizeof(screen_char_t)); 922 } 923 sb_length+=num; 924 if (sb_length>max_scrollback) 925 sb_length=max_scrollback; 926 } 927 928 if (t+nr >= b) 929 nr = b - t - 1; 930 if (b > sy || t >= b || nr < 1) 931 return; 932 d = &SCREEN(0,t); 933 s = &SCREEN(0,t+nr); 934 935 if (current_y>=t && current_y<=b) 936 { 937 SCREEN(current_x,current_y).attr|=0x80; 938 draw_cursor=YES; 939 /* 940 TODO: does this properly handle the case when the cursor is in 941 an area that gets scrolled 'over'? 942 943 now it does, but not in an optimal way. handling of this could be 944 optimized in all scrolling methods, but it probably won't make 945 much difference 946 */ 947 } 948 memmove(d, s, (b-t-nr) * sx * sizeof(screen_char_t)); 949 if (!current_scroll) 950 { 951 if (t==0 && b==sy) 952 { 953 pending_scroll-=nr; 954 } 955 else 956 { 957 float x0,y0,w,h,dx,dy; 958 959 if (pending_scroll) 960 [self _handlePendingScroll: YES]; 961 962 x0=0; 963 w=fx*sx; 964 y0=(t+nr)*fy; 965 h=(b-t-nr)*fy; 966 dx=0; 967 dy=t*fy; 968 y0=sy*fy-y0-h; 969 dy=sy*fy-dy-h; 970 [self lockFocus]; 971 DPScomposite(GSCurrentContext(),border_x+x0,border_y+y0,w,h, 972 [self gState],border_x+dx,border_y+dy,NSCompositeCopy); 973 [self unlockFocusNeedsFlush: NO]; 974 num_scrolls++; 975 } 976 } 977 ADD_DIRTY(0,t,sx,b-t); 978} 979 980-(void) ts_scrollDown: (int)t :(int)b rows: (int)nr 981{ 982 screen_char_t *s; 983 unsigned int step; 984 985 NSDebugLLog(@"ts",@"scrollDown: %i:%i rows: %i", 986 t,b,nr); 987 988 if (t+nr >= b) 989 nr = b - t - 1; 990 if (b > sy || t >= b || nr < 1) 991 return; 992 s = &SCREEN(0,t); 993 step = sx * nr; 994 if (current_y>=t && current_y<=b) 995 { 996 SCREEN(current_x,current_y).attr|=0x80; 997 draw_cursor=YES; 998 } 999 memmove(s + step, s, (b-t-nr)*sx*sizeof(screen_char_t)); 1000 if (!current_scroll) 1001 { 1002 if (t==0 && b==sy) 1003 { 1004 pending_scroll+=nr; 1005 } 1006 else 1007 { 1008 float x0,y0,w,h,dx,dy; 1009 1010 if (pending_scroll) 1011 [self _handlePendingScroll: YES]; 1012 1013 x0=0; 1014 w=fx*sx; 1015 y0=(t)*fy; 1016 h=(b-t-nr)*fy; 1017 dx=0; 1018 dy=(t+nr)*fy; 1019 y0=sy*fy-y0-h; 1020 dy=sy*fy-dy-h; 1021 [self lockFocus]; 1022 DPScomposite(GSCurrentContext(),border_x+x0,border_y+y0,w,h, 1023 [self gState],border_x+dx,border_y+dy,NSCompositeCopy); 1024 [self unlockFocusNeedsFlush: NO]; 1025 num_scrolls++; 1026 } 1027 } 1028 ADD_DIRTY(0,t,sx,b-t); 1029} 1030 1031-(void) ts_shiftRow: (int)y at: (int)x0 delta: (int)delta 1032{ 1033 screen_char_t *s,*d; 1034 int x1,c; 1035 NSDebugLLog(@"ts",@"shiftRow: %i at: %i delta: %i", 1036 y,x0,delta); 1037 1038 if (y<0 || y>=sy) return; 1039 if (x0<0 || x0>=sx) return; 1040 1041 if (current_y==y) 1042 { 1043 SCREEN(current_x,current_y).attr|=0x80; 1044 draw_cursor=YES; 1045 } 1046 1047 s=&SCREEN(x0,y); 1048 x1=x0+delta; 1049 c=sx-x0; 1050 if (x1<0) 1051 { 1052 x0-=x1; 1053 c+=x1; 1054 x1=0; 1055 } 1056 if (x1+c>sx) 1057 c=sx-x1; 1058 d=&SCREEN(x1,y); 1059 memmove(d,s,sizeof(screen_char_t)*c); 1060 if (!current_scroll) 1061 { 1062 float cx0,y0,w,h,dx,dy; 1063 1064 if (pending_scroll) 1065 [self _handlePendingScroll: YES]; 1066 1067 cx0=x0*fx; 1068 w=fx*c; 1069 dx=x1*fx; 1070 1071 y0=y*fy; 1072 h=fy; 1073 dy=y0; 1074 1075 y0=sy*fy-y0-h; 1076 dy=sy*fy-dy-h; 1077 [self lockFocus]; 1078 DPScomposite(GSCurrentContext(),border_x+cx0,border_y+y0,w,h, 1079 [self gState],border_x+dx,border_y+dy,NSCompositeCopy); 1080 [self unlockFocusNeedsFlush: NO]; 1081 num_scrolls++; 1082 } 1083 ADD_DIRTY(0,y,sx,1); 1084} 1085 1086-(screen_char_t) ts_getCharAt: (int)x :(int)y 1087{ 1088 NSDebugLLog(@"ts",@"getCharAt: %i:%i",x,y); 1089 return SCREEN(x,y); 1090} 1091 1092 1093-(void) addDataToWriteBuffer: (const char *)data 1094 length: (int)len 1095{ 1096 if (!len) 1097 return; 1098 1099 if (!write_buf_len) 1100 { 1101 [[NSRunLoop currentRunLoop] 1102 addEvent: (void *)(intptr_t)master_fd 1103 type: ET_WDESC 1104 watcher: self 1105 forMode: NSDefaultRunLoopMode]; 1106 } 1107 1108 if (write_buf_len+len>write_buf_size) 1109 { 1110 /* Round up to nearest multiple of 512 bytes. */ 1111 write_buf_size=(write_buf_len+len+511)&~511; 1112 write_buf=realloc(write_buf,write_buf_size); 1113 } 1114 memcpy(&write_buf[write_buf_len],data,len); 1115 write_buf_len+=len; 1116} 1117 1118-(void) ts_sendCString: (const char *)msg 1119{ 1120 [self ts_sendCString: msg length: strlen(msg)]; 1121} 1122-(void) ts_sendCString: (const char *)msg length: (int)len 1123{ 1124 int l; 1125 if (master_fd==-1) 1126 return; 1127 1128 if (write_buf_len) 1129 { 1130 [self addDataToWriteBuffer: msg length: len]; 1131 return; 1132 } 1133 1134 l=write(master_fd,msg,len); 1135 if (l!=len) 1136 { 1137 if (errno!=EAGAIN) 1138 NSLog(_(@"Unexpected error while writing.")); 1139 if (l<0) 1140 l=0; 1141 [self addDataToWriteBuffer: &msg[l] length: len-l]; 1142 } 1143} 1144 1145 1146-(BOOL) useMultiCellGlyphs 1147{ 1148 return use_multi_cell_glyphs; 1149} 1150 1151-(int) relativeWidthOfCharacter: (unichar)ch 1152{ 1153 int s; 1154 if (!use_multi_cell_glyphs) 1155 return 1; 1156 s=ceil([font boundingRectForGlyph: ch].size.width/fx); 1157 if (s<1) 1158 return 1; 1159 return s; 1160} 1161 1162 1163-(void) viewPrefsDidChange: (NSNotification *)n 1164{ 1165 /* TODO: handle font changes? */ 1166 [self setNeedsDisplay: YES]; 1167} 1168 1169@end 1170 1171 1172/** 1173Scrolling 1174**/ 1175 1176@implementation TerminalView (scrolling) 1177 1178-(void) _updateScroller 1179{ 1180 if (sb_length) 1181 { 1182 [scroller setEnabled: YES]; 1183 [scroller setFloatValue: (current_scroll+sb_length)/(float)(sb_length) 1184 knobProportion: sy/(float)(sy+sb_length)]; 1185 } 1186 else 1187 { 1188 [scroller setEnabled: NO]; 1189 } 1190} 1191 1192-(void) _scrollTo: (int)new_scroll update: (BOOL)update 1193{ 1194 if (new_scroll>0) 1195 new_scroll=0; 1196 if (new_scroll<-sb_length) 1197 new_scroll=-sb_length; 1198 1199 if (new_scroll==current_scroll) 1200 return; 1201 current_scroll=new_scroll; 1202 1203 if (update) 1204 { 1205 [self _updateScroller]; 1206 } 1207 1208 [self setNeedsDisplay: YES]; 1209} 1210 1211-(void) scrollWheel: (NSEvent *)e 1212{ 1213 float delta=[e deltaY]; 1214 int new_scroll; 1215 int mult; 1216 1217 if ([e modifierFlags]&NSShiftKeyMask) 1218 mult=1; 1219 else if ([e modifierFlags]&NSControlKeyMask) 1220 mult=sy; 1221 else 1222 mult=5; 1223 1224 new_scroll=current_scroll-delta*mult; 1225 [self _scrollTo: new_scroll update: YES]; 1226} 1227 1228-(void) _updateScroll: (id)sender 1229{ 1230 int new_scroll; 1231 int part=[scroller hitPart]; 1232 BOOL update=YES; 1233 1234 if (part==NSScrollerKnob || 1235 part==NSScrollerKnobSlot) 1236 { 1237 float f=[scroller floatValue]; 1238 new_scroll=(f-1.0)*sb_length; 1239 update=NO; 1240 } 1241 else if (part==NSScrollerDecrementLine) 1242 new_scroll=current_scroll-1; 1243 else if (part==NSScrollerDecrementPage) 1244 new_scroll=current_scroll-sy/2; 1245 else if (part==NSScrollerIncrementLine) 1246 new_scroll=current_scroll+1; 1247 else if (part==NSScrollerIncrementPage) 1248 new_scroll=current_scroll+sy/2; 1249 else 1250 return; 1251 1252 [self _scrollTo: new_scroll update: update]; 1253} 1254 1255-(void) setScroller: (NSScroller *)sc 1256{ 1257 [scroller setTarget: nil]; 1258 ASSIGN(scroller,sc); 1259 [self _updateScroller]; 1260 [scroller setTarget: self]; 1261 [scroller setAction: @selector(_updateScroll:)]; 1262} 1263 1264@end 1265 1266 1267/** 1268Keyboard events 1269**/ 1270 1271@implementation TerminalView (keyboard) 1272 1273-(void) keyDown: (NSEvent *)e 1274{ 1275 NSString *s=[e charactersIgnoringModifiers]; 1276 1277 NSDebugLLog(@"key",@"got key flags=%08x repeat=%i '%@' '%@' %4i %04x %li %04x %li\n", 1278 (unsigned int)[e modifierFlags],(int)[e isARepeat],[e characters],[e charactersIgnoringModifiers],[e keyCode], 1279 [[e characters] characterAtIndex: 0],[[e characters] length], 1280 [[e charactersIgnoringModifiers] characterAtIndex: 0],[[e charactersIgnoringModifiers] length]); 1281 1282 if ([s length]==1 && ([e modifierFlags]&NSShiftKeyMask)) 1283 { 1284 unichar ch=[s characterAtIndex: 0]; 1285 if (ch==NSPageUpFunctionKey) 1286 { 1287 [self _scrollTo: current_scroll-sy+1 update: YES]; 1288 return; 1289 } 1290 if (ch==NSPageDownFunctionKey) 1291 { 1292 [self _scrollTo: current_scroll+sy-1 update: YES]; 1293 return; 1294 } 1295 } 1296 1297 /* don't check until we get here so we handle scrollback page-up/down 1298 even when the view's idle */ 1299 if (master_fd==-1) 1300 return; 1301 1302 [tp handleKeyEvent: e]; 1303} 1304 1305-(BOOL) acceptsFirstResponder 1306{ 1307 return YES; 1308} 1309-(BOOL) becomeFirstResponder 1310{ 1311 return YES; 1312} 1313-(BOOL) resignFirstResponder 1314{ 1315 return YES; 1316} 1317 1318@end 1319 1320 1321/** 1322Selection, copy/paste/services 1323**/ 1324 1325@implementation TerminalView (selection) 1326 1327-(NSString *) _selectionAsString 1328{ 1329 int ofs=max_scrollback*sx; 1330 NSMutableString *mstr; 1331 NSString *tmp; 1332 unichar buf[32]; 1333 unichar ch; 1334 int len,ws_len; 1335 int i,j; 1336 1337 if (selection.length==0) 1338 return nil; 1339 1340 mstr=[[NSMutableString alloc] init]; 1341 j=selection.location+selection.length; 1342 len=0; 1343 for (i=selection.location;i<j;i++) 1344 { 1345 ws_len=0; 1346 while (1) 1347 { 1348 if (i<0) 1349 ch=sbuf[ofs+i].ch; 1350 else 1351 ch=screen[i].ch; 1352 1353 if (ch!=' ' && ch!=0 && ch!=MULTI_CELL_GLYPH) 1354 break; 1355 ws_len++; 1356 i++; 1357 1358 if (i%sx==0) 1359 { 1360 if (i>j) 1361 { 1362 ws_len=0; /* make sure we break out of the outer loop */ 1363 break; 1364 } 1365 if (len) 1366 { 1367 tmp=[[NSString alloc] initWithCharacters: buf length: len]; 1368 [mstr appendString: tmp]; 1369 DESTROY(tmp); 1370 len=0; 1371 } 1372 [mstr appendString: @"\n"]; 1373 ws_len=0; 1374 continue; 1375 } 1376 } 1377 1378 i-=ws_len; 1379 1380 for (;i<j && ws_len;i++,ws_len--) 1381 { 1382 buf[len++]=' '; 1383 if (len==32) 1384 { 1385 tmp=[[NSString alloc] initWithCharacters: buf length: 32]; 1386 [mstr appendString: tmp]; 1387 DESTROY(tmp); 1388 len=0; 1389 } 1390 } 1391 if (i>=j) 1392 break; 1393 1394 buf[len++]=ch; 1395 if (len==32) 1396 { 1397 tmp=[[NSString alloc] initWithCharacters: buf length: 32]; 1398 [mstr appendString: tmp]; 1399 DESTROY(tmp); 1400 len=0; 1401 } 1402 } 1403 1404 if (len) 1405 { 1406 tmp=[[NSString alloc] initWithCharacters: buf length: len]; 1407 [mstr appendString: tmp]; 1408 DESTROY(tmp); 1409 } 1410 1411 return AUTORELEASE(mstr); 1412} 1413 1414 1415-(void) _setSelection: (struct selection_range)s 1416{ 1417 int i,j,ofs2; 1418 1419 if (s.location<-sb_length*sx) 1420 { 1421 s.length+=sb_length*sx+s.location; 1422 s.location=-sb_length*sx; 1423 } 1424 if (s.location+s.length>sx*sy) 1425 { 1426 s.length=sx*sy-s.location; 1427 } 1428 1429 if (!s.length && !selection.length) 1430 return; 1431 if (s.length==selection.length && s.location==selection.location) 1432 return; 1433 1434 ofs2=max_scrollback*sx; 1435 1436 j=selection.location+selection.length; 1437 if (j>s.location) 1438 j=s.location; 1439 1440 for (i=selection.location;i<j && i<0;i++) 1441 { 1442 sbuf[ofs2+i].attr&=0xbf; 1443 sbuf[ofs2+i].attr|=0x80; 1444 } 1445 for (;i<j;i++) 1446 { 1447 screen[i].attr&=0xbf; 1448 screen[i].attr|=0x80; 1449 } 1450 1451 i=s.location+s.length; 1452 if (i<selection.location) 1453 i=selection.location; 1454 j=selection.location+selection.length; 1455 for (;i<j && i<0;i++) 1456 { 1457 sbuf[ofs2+i].attr&=0xbf; 1458 sbuf[ofs2+i].attr|=0x80; 1459 } 1460 for (;i<j;i++) 1461 { 1462 screen[i].attr&=0xbf; 1463 screen[i].attr|=0x80; 1464 } 1465 1466 i=s.location; 1467 j=s.location+s.length; 1468 for (;i<j && i<0;i++) 1469 { 1470 if (!(sbuf[ofs2+i].attr&0x40)) 1471 sbuf[ofs2+i].attr|=0xc0; 1472 } 1473 for (;i<j;i++) 1474 { 1475 if (!(screen[i].attr&0x40)) 1476 screen[i].attr|=0xc0; 1477 } 1478 1479 selection=s; 1480 [self setNeedsLazyDisplayInRect: [self bounds]]; 1481} 1482 1483-(void) _clearSelection 1484{ 1485 struct selection_range s; 1486 s.location=s.length=0; 1487 [self _setSelection: s]; 1488} 1489 1490 1491-(void) copy: (id)sender 1492{ 1493 NSPasteboard *pb=[NSPasteboard generalPasteboard]; 1494 NSString *s=[self _selectionAsString]; 1495 if (!s) 1496 { 1497 NSBeep(); 1498 return; 1499 } 1500 [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType] 1501 owner: self]; 1502 [pb setString: s forType: NSStringPboardType]; 1503} 1504 1505-(void) paste: (id)sender 1506{ 1507 NSPasteboard *pb=[NSPasteboard generalPasteboard]; 1508 NSString *type; 1509 NSString *str; 1510 1511 type=[pb availableTypeFromArray: [NSArray arrayWithObject: NSStringPboardType]]; 1512 if (!type) 1513 return; 1514 str=[pb stringForType: NSStringPboardType]; 1515 if (str) 1516 [tp sendString: str]; 1517} 1518 1519-(BOOL) writeSelectionToPasteboard: (NSPasteboard *)pb 1520 types: (NSArray *)t 1521{ 1522 int i; 1523 NSString *s; 1524 1525 s=[self _selectionAsString]; 1526 if (!s) 1527 { 1528 NSBeep(); 1529 return NO; 1530 } 1531 1532 [pb declareTypes: t owner: self]; 1533 for (i=0;i<[t count];i++) 1534 { 1535 if ([[t objectAtIndex: i] isEqual: NSStringPboardType]) 1536 { 1537 [pb setString: s 1538 forType: NSStringPboardType]; 1539 return YES; 1540 } 1541 } 1542 return NO; 1543} 1544 1545-(BOOL) readSelectionFromPasteboard: (NSPasteboard *)pb 1546{ /* TODO: is it really necessary to implement this? */ 1547 return YES; 1548} 1549 1550-(id) validRequestorForSendType: (NSString *)st 1551 returnType: (NSString *)rt 1552{ 1553 if (!selection.length) 1554 return nil; 1555 if (st!=nil && ![st isEqual: NSStringPboardType]) 1556 return nil; 1557 if (rt!=nil) 1558 return nil; 1559 return self; 1560} 1561 1562 1563/* Return the range we should select for the given position and granularity: 1564 0 characters 1565 1 words 1566 2 lines 1567*/ 1568-(struct selection_range) _selectionRangeAt: (int)pos granularity: (int)g 1569{ 1570 struct selection_range s; 1571 1572 if (g==3) 1573 { /* select lines */ 1574 int l=floor(pos/(float)sx); 1575 s.location=l*sx; 1576 s.length=sx; 1577 return s; 1578 } 1579 1580 if (g==2) 1581 { /* select words */ 1582 int ofs=max_scrollback*sx; 1583 unichar ch,ch2; 1584 NSCharacterSet *cs; 1585 int i,j; 1586 1587 if (pos<0) 1588 ch=sbuf[ofs+pos].ch; 1589 else 1590 ch=screen[pos].ch; 1591 if (ch==0) ch=' '; 1592 1593 /* try to find a character set for this character */ 1594 cs=[NSCharacterSet alphanumericCharacterSet]; 1595 if (![cs characterIsMember: ch]) 1596 cs=[NSCharacterSet punctuationCharacterSet]; 1597 if (![cs characterIsMember: ch]) 1598 cs=[NSCharacterSet whitespaceCharacterSet]; 1599 if (![cs characterIsMember: ch]) 1600 { 1601 s.location=pos; 1602 s.length=1; 1603 return s; 1604 } 1605 1606 /* search the line backwards for a boundary */ 1607 j=floor(pos/(float)sx); 1608 j*=sx; 1609 for (i=pos-1;i>=j;i--) 1610 { 1611 if (i<0) 1612 ch2=sbuf[ofs+i].ch; 1613 else 1614 ch2=screen[i].ch; 1615 if (ch2==0) ch2=' '; 1616 1617 if (![cs characterIsMember: ch2]) 1618 break; 1619 } 1620 s.location=i+1; 1621 1622 /* and forwards... */ 1623 j+=sx; 1624 for (i=pos+1;i<j;i++) 1625 { 1626 if (i<0) 1627 ch2=sbuf[ofs+i].ch; 1628 else 1629 ch2=screen[i].ch; 1630 if (ch2==0) ch2=' '; 1631 1632 if (![cs characterIsMember: ch2]) 1633 break; 1634 } 1635 s.length=i-s.location; 1636 return s; 1637 } 1638 1639 s.location=pos; 1640 s.length=0; 1641 1642 return s; 1643} 1644 1645-(void) mouseDown: (NSEvent *)e 1646{ 1647 int ofs0,ofs1,first; 1648 NSPoint p; 1649 struct selection_range s; 1650 int g; 1651 struct selection_range r0,r1; 1652 1653 1654 r0.location=r0.length=0; 1655 first=YES; 1656 ofs0=0; /* get compiler to shut up */ 1657 g=[e clickCount]; 1658 while ([e type]!=NSLeftMouseUp) 1659 { 1660 p=[e locationInWindow]; 1661 1662 p=[self convertPoint: p fromView: nil]; 1663 p.x=floor((p.x-border_x)/fx); 1664 if (p.x<0) p.x=0; 1665 if (p.x>=sx) p.x=sx-1; 1666 p.y=ceil((p.y-border_y)/fy); 1667 if (p.y<-1) p.y=-1; 1668 if (p.y>sy) p.y=sy; 1669 p.y=sy-p.y+current_scroll; 1670 ofs1=((int)p.x)+((int)p.y)*sx; 1671 1672 r1=[self _selectionRangeAt: ofs1 granularity: g]; 1673 if (first) 1674 { 1675 ofs0=ofs1; 1676 first=0; 1677 r0=r1; 1678 } 1679 1680 NSDebugLLog(@"select",@"ofs %i %i (%i+%i) (%i+%i)\n", 1681 ofs0,ofs1, 1682 r0.location,r0.length, 1683 r1.location,r1.length); 1684 1685 if (ofs1>ofs0) 1686 { 1687 s.location=r0.location; 1688 s.length=r1.location+r1.length-r0.location; 1689 } 1690 else 1691 { 1692 s.location=r1.location; 1693 s.length=r0.location+r0.length-r1.location; 1694 } 1695 1696 [self _setSelection: s]; 1697 [self displayIfNeeded]; 1698 1699 e=[NSApp nextEventMatchingMask: NSLeftMouseDownMask|NSLeftMouseUpMask| 1700 NSLeftMouseDraggedMask|NSMouseMovedMask 1701 untilDate: [NSDate distantFuture] 1702 inMode: NSEventTrackingRunLoopMode 1703 dequeue: YES]; 1704 } 1705 1706 if (selection.length) 1707 { 1708 [self writeSelectionToPasteboard: [NSPasteboard pasteboardWithName: @"Selection"] 1709 types: [NSArray arrayWithObject: NSStringPboardType]]; 1710 } 1711} 1712 1713-(void) otherMouseUp: (NSEvent *)e 1714{ 1715 NSPasteboard *pb=[NSPasteboard pasteboardWithName: @"Selection"]; 1716 NSString *type; 1717 NSString *str; 1718 1719 type=[pb availableTypeFromArray: [NSArray arrayWithObject: NSStringPboardType]]; 1720 if (!type) 1721 return; 1722 str=[pb stringForType: NSStringPboardType]; 1723 if (str) 1724 [tp sendString: str]; 1725} 1726 1727@end 1728 1729 1730/** 1731Handle master_fd 1732**/ 1733 1734@implementation TerminalView (input) 1735 1736-(NSDate *) timedOutEvent: (void *)data type: (RunLoopEventType)t 1737 forMode: (NSString *)mode 1738{ 1739 NSLog(@"timedOutEvent:type:forMode: ignored"); 1740 return nil; 1741} 1742 1743-(void) readData 1744{ 1745 char buf[256]; 1746 int size,total,i; 1747 1748// get_zombies(); 1749 1750 total=0; 1751 num_scrolls=0; 1752 dirty.x0=-1; 1753 1754 current_x=cursor_x; 1755 current_y=cursor_y; 1756 1757 [self _clearSelection]; /* TODO? */ 1758 1759 NSDebugLLog(@"term",@"receiving output"); 1760 1761 while (1) 1762 { 1763 size=read(master_fd,buf,sizeof(buf)); 1764 if (size<0 && errno==EAGAIN) 1765 break; 1766 if (size<=0) 1767 { 1768 NSString *msg; 1769 int i,c; 1770 unichar ch; 1771 1772// get_zombies(); 1773 [self closeProgram]; 1774 1775 msg=_(@"[Process exited]"); 1776 c=[msg length]; 1777 for (i=0;i<c;i++) 1778 { 1779 ch=[msg characterAtIndex: i]; 1780 if (ch<256) /* TODO */ 1781 [tp processByte: ch]; 1782 } 1783 [tp processByte: '\n']; 1784 [tp processByte: '\r']; 1785 1786 /* Sending this notification might cause us to be deallocated, in 1787 which case we can't let the rest of code here run (and we'd rather 1788 not to avoid a pointless update of the screen). To detect this, we 1789 retain ourself before the call and check the retaincount after. */ 1790 [self retain]; 1791 [[NSNotificationCenter defaultCenter] 1792 postNotificationName: TerminalViewBecameIdleNotification 1793 object: self]; 1794 if ([self retainCount]==1) 1795 { /* we only have our own retain left, so we release ourself 1796 (causing us to be deallocated) and return */ 1797 [self release]; 1798 return; 1799 } 1800 [self release]; 1801 1802 break; 1803 } 1804 1805 1806 for (i=0;i<size;i++) 1807 [tp processByte: buf[i]]; 1808 1809 total+=size; 1810 /* 1811 Don't get stuck processing input forever; give other terminal windows 1812 and the user a chance to do things. The numbers affect latency versus 1813 throughput. High numbers means more input is processed before the 1814 screen is updated, leading to higher throughput but also to more 1815 'jerky' updates. Low numbers would give smoother updating and less 1816 latency, but throughput goes down. 1817 1818 TODO: tweak more? seems pretty good now 1819 */ 1820 if (total>=8192 || (num_scrolls+abs(pending_scroll))>10) 1821 break; 1822 } 1823 1824 if (cursor_x!=current_x || cursor_y!=current_y) 1825 { 1826 ADD_DIRTY(current_x,current_y,1,1); 1827 SCREEN(current_x,current_y).attr|=0x80; 1828 ADD_DIRTY(cursor_x,cursor_y,1,1); 1829 draw_cursor=YES; 1830 } 1831 1832 NSDebugLLog(@"term",@"done (%i %i) (%i %i)\n", 1833 dirty.x0,dirty.y0,dirty.x1,dirty.y1); 1834 1835 if (dirty.x0>=0) 1836 { 1837 NSRect dr; 1838 1839// NSLog(@"dirty=(%i %i)-(%i %i)\n",dirty.x0,dirty.y0,dirty.x1,dirty.y1); 1840 dr.origin.x=dirty.x0*fx; 1841 dr.origin.y=dirty.y0*fy; 1842 dr.size.width=(dirty.x1-dirty.x0)*fx; 1843 dr.size.height=(dirty.y1-dirty.y0)*fy; 1844 dr.origin.y=fy*sy-(dr.origin.y+dr.size.height); 1845// NSLog(@"-> dirty=(%g %g)+(%g %g)\n",dirty.origin.x,dirty.origin.y,dirty.size.width,dirty.size.height); 1846 dr.origin.x+=border_x; 1847 dr.origin.y+=border_y; 1848 [self setNeedsLazyDisplayInRect: dr]; 1849 1850 if (current_scroll!=0) 1851 { /* TODO */ 1852 current_scroll=0; 1853 [self setNeedsDisplay: YES]; 1854 } 1855 1856 [self _updateScroller]; 1857 } 1858} 1859 1860-(void) writePendingData 1861{ 1862 int l,new_size; 1863 l=write(master_fd,write_buf,write_buf_len); 1864 if (l<0) 1865 { 1866 if (errno!=EAGAIN) 1867 NSLog(_(@"Unexpected error while writing.")); 1868 return; 1869 } 1870 memmove(write_buf,&write_buf[l],write_buf_len-l); 1871 write_buf_len-=l; 1872 1873 /* If less than half the buffer is empty, reallocate it, but never free 1874 it completely. */ 1875 new_size=(write_buf_len+511)&~511; 1876 if (!new_size) 1877 new_size=512; 1878 if (new_size<=write_buf_size/2) 1879 { 1880 write_buf_size=new_size; 1881 write_buf=realloc(write_buf,write_buf_size); 1882 } 1883 1884 if (!write_buf_len) 1885 { 1886 [[NSRunLoop currentRunLoop] removeEvent: (void *)(intptr_t)master_fd 1887 type: ET_WDESC 1888 forMode: NSDefaultRunLoopMode 1889 all: YES]; 1890 } 1891} 1892 1893-(void) receivedEvent: (void *)data 1894 type: (RunLoopEventType)type 1895 extra: (void *)extra 1896 forMode: (NSString *)mode 1897{ 1898 if (type==ET_WDESC) 1899 [self writePendingData]; 1900 else if (type==ET_RDESC) 1901 [self readData]; 1902} 1903 1904 1905-(void) closeProgram 1906{ 1907 if (master_fd==-1) 1908 return; 1909 NSDebugLLog(@"pty",@"closing master fd=%i\n",master_fd); 1910 [[NSRunLoop currentRunLoop] removeEvent: (void *)(intptr_t)master_fd 1911 type: ET_RDESC 1912 forMode: NSDefaultRunLoopMode 1913 all: YES]; 1914 [[NSRunLoop currentRunLoop] removeEvent: (void *)(intptr_t)master_fd 1915 type: ET_WDESC 1916 forMode: NSDefaultRunLoopMode 1917 all: YES]; 1918 write_buf_len=write_buf_size=0; 1919 free(write_buf); 1920 write_buf=NULL; 1921 close(master_fd); 1922 master_fd=-1; 1923} 1924 1925 1926-(void) runProgram: (NSString *)path 1927 withArguments: (NSArray *)args 1928 inDirectory: (NSString *)directory 1929 initialInput: (NSString *)d 1930 arg0: (NSString *)arg0 1931{ 1932 int ret; 1933 struct winsize ws; 1934 NSRunLoop *rl; 1935 const char *cpath; 1936 const char *cargs[[args count]+2]; 1937 const char *cdirectory; 1938 int i; 1939 int pipefd[2]; 1940 int flags; 1941 1942 NSDebugLLog(@"pty",@"-runProgram: %@ withArguments: %@ initialInput: %@", 1943 path,args,d); 1944 1945 [self closeProgram]; 1946 1947 cpath=[path cString]; 1948 if (arg0) 1949 cargs[0]=[arg0 cString]; 1950 else 1951 cargs[0]=cpath; 1952 cdirectory=[directory cString]; 1953 for (i=0;i<[args count];i++) 1954 { 1955 cargs[i+1]=[[args objectAtIndex: i] cString]; 1956 } 1957 cargs[i+1]=NULL; 1958 1959 if (d) 1960 { 1961 if (pipe(pipefd)) 1962 { 1963 NSLog(_(@"Unable to open pipe for input.")); 1964 return; 1965 } 1966 NSDebugLLog(@"pty",@"creating pipe for initial data, got %i %i", 1967 pipefd[0],pipefd[1]); 1968 } 1969 1970 ws.ws_row=sy; 1971 ws.ws_col=sx; 1972 ret=forkpty(&master_fd,NULL,NULL,&ws); 1973 if (ret<0) 1974 { 1975 NSLog(_(@"Unable to fork.")); 1976 return; 1977 } 1978 1979 if (ret==0) 1980 { 1981 if (d) 1982 { 1983 close(pipefd[1]); 1984 dup2(pipefd[0],0); 1985 } 1986 1987 if (cdirectory) 1988 if (chdir(cdirectory) < 0) 1989 fprintf(stderr, "Unable do set directory: %s\n", cdirectory); 1990 putenv("TERM=linux"); 1991 putenv("TERM_PROGRAM=GNUstep_Terminal"); 1992 execv(cpath,(char *const*)cargs); 1993 fprintf(stderr,"Unable to spawn process '%s': %m!",cpath); 1994 exit(1); 1995 } 1996 1997 NSDebugLLog(@"pty",@"forked child %i, fd %i",ret,master_fd); 1998 1999 /* Set non-blocking mode for the descriptor. */ 2000 flags=fcntl(master_fd,F_GETFL,0); 2001 if (flags==-1) 2002 { 2003 NSLog(_(@"Unable to set non-blocking mode.")); 2004 } 2005 else 2006 { 2007 flags|=O_NONBLOCK; 2008 fcntl(master_fd,F_SETFL,flags); 2009 } 2010 2011 rl=[NSRunLoop currentRunLoop]; 2012 [rl addEvent: (void *)master_fd 2013 type: ET_RDESC 2014 watcher: self 2015 forMode: NSDefaultRunLoopMode]; 2016 2017 [[NSNotificationCenter defaultCenter] 2018 postNotificationName: TerminalViewBecameNonIdleNotification 2019 object: self]; 2020 2021 if (d) 2022 { 2023 const char *s=[d UTF8String]; 2024 close(pipefd[0]); 2025 write(pipefd[1],s,strlen(s)); 2026 close(pipefd[1]); 2027 } 2028 2029 DESTROY(title_window); 2030 if (args) 2031 title_window=[[NSString stringWithFormat: @"%@ %@", 2032 path,[args componentsJoinedByString: @" "]] retain]; 2033 else 2034 title_window=[path copy]; 2035 2036 ASSIGN(title_miniwindow,path); 2037 [[NSNotificationCenter defaultCenter] 2038 postNotificationName: TerminalViewTitleDidChangeNotification 2039 object: self]; 2040} 2041 2042-(void) runProgram: (NSString *)path 2043 withArguments: (NSArray *)args 2044 initialInput: (NSString *)d 2045{ 2046 [self runProgram: path 2047 withArguments: args 2048 inDirectory: nil 2049 initialInput: d 2050 arg0: path]; 2051} 2052 2053-(void) runShell 2054{ 2055 NSString *arg0; 2056 NSString *path; 2057 2058 path=[TerminalViewShellPrefs shell]; 2059 if ([TerminalViewShellPrefs loginShell]) 2060 arg0=[@"-" stringByAppendingString: path]; 2061 else 2062 arg0=path; 2063 [self runProgram: path 2064 withArguments: nil 2065 inDirectory: nil 2066 initialInput: nil 2067 arg0: arg0]; 2068} 2069 2070@end 2071 2072 2073/** 2074drag'n'drop support 2075**/ 2076 2077@implementation TerminalView (drag_n_drop) 2078 2079static int handled_mask= 2080 NSDragOperationCopy|NSDragOperationPrivate|NSDragOperationGeneric; 2081 2082-(unsigned int) draggingEntered: (id<NSDraggingInfo>)sender 2083{ 2084 NSArray *types=[[sender draggingPasteboard] types]; 2085 unsigned int mask=[sender draggingSourceOperationMask]; 2086 2087 NSDebugLLog(@"dragndrop",@"TerminalView draggingEntered mask=%x types=%@",mask,types); 2088 2089 if (mask&handled_mask && 2090 ([types containsObject: NSFilenamesPboardType] || 2091 [types containsObject: NSStringPboardType])) 2092 return NSDragOperationCopy; 2093 return 0; 2094} 2095 2096/* TODO: should I really have to implement this? */ 2097-(BOOL) prepareForDragOperation: (id<NSDraggingInfo>)sender 2098{ 2099 NSDebugLLog(@"dragndrop",@"preparing for drag"); 2100 return YES; 2101} 2102 2103-(BOOL) performDragOperation: (id<NSDraggingInfo>)sender 2104{ 2105 NSPasteboard *pb=[sender draggingPasteboard]; 2106 NSArray *types=[pb types]; 2107 unsigned int mask=[sender draggingSourceOperationMask]; 2108 2109 NSDebugLLog(@"dragndrop",@"performDrag %x %@",mask,types); 2110 2111 if (!(mask&handled_mask)) 2112 return NO; 2113 2114 if ([types containsObject: NSFilenamesPboardType]) 2115 { 2116 NSArray *data; 2117 int i,c; 2118 2119 data=[pb propertyListForType: NSFilenamesPboardType]; 2120 if (!data) 2121 data=[NSUnarchiver unarchiveObjectWithData: [pb dataForType: NSFilenamesPboardType]]; 2122 2123 c=[data count]; 2124 2125 for (i=0;i<c;i++) 2126 { 2127 [tp sendString: @" "]; 2128 [tp sendString: [data objectAtIndex: i]]; 2129 } 2130 return YES; 2131 } 2132 2133 if ([types containsObject: NSStringPboardType]) 2134 { 2135 NSString *str=[pb stringForType: NSStringPboardType]; 2136 [tp sendString: str]; 2137 return YES; 2138 } 2139 2140 return NO; 2141} 2142 2143@end 2144 2145 2146/** 2147misc. stuff 2148**/ 2149 2150@implementation TerminalView 2151 2152-(void) _resizeTerminalTo: (NSSize)size 2153{ 2154 int nsx,nsy; 2155 struct winsize ws; 2156 screen_char_t *nscreen,*nsbuf; 2157 int iy,ny; 2158 int copy_sx; 2159 2160 nsx=(size.width-border_x)/fx; 2161 nsy=(size.height-border_y)/fy; 2162 2163 NSDebugLLog(@"term",@"_resizeTerminalTo: (%g %g) %i %i (%g %g)\n", 2164 size.width,size.height, 2165 nsx,nsy, 2166 nsx*fx,nsy*fy); 2167 2168 if (ignore_resize) 2169 { 2170 NSDebugLLog(@"term",@"ignored"); 2171 return; 2172 } 2173 2174 if (nsx<1) nsx=1; 2175 if (nsy<1) nsy=1; 2176 2177 if (nsx==sx && nsy==sy) 2178 { 2179 /* Do a complete redraw anyway. Even though we don't really need it, 2180 the resize might have caused other things to overwrite our part of the 2181 window. */ 2182 draw_all=2; 2183 return; 2184 } 2185 2186 [self _clearSelection]; /* TODO? */ 2187 2188 nscreen=malloc(nsx*nsy*sizeof(screen_char_t)); 2189 nsbuf=malloc(nsx*max_scrollback*sizeof(screen_char_t)); 2190 if (!nscreen || !nsbuf) 2191 { 2192 NSLog(@"Failed to allocate screen buffer!"); 2193 if (nscreen) 2194 free(nscreen); 2195 if (nsbuf) 2196 free(nsbuf); 2197 return; 2198 } 2199 memset(nscreen,0,sizeof(screen_char_t)*nsx*nsy); 2200 memset(nsbuf,0,sizeof(screen_char_t)*nsx*max_scrollback); 2201 2202 copy_sx=sx; 2203 if (copy_sx>nsx) 2204 copy_sx=nsx; 2205 2206// NSLog(@"copy %i+%i %i (%ix%i)-(%ix%i)\n",start,num,copy_sx,sx,sy,nsx,nsy); 2207 2208/* TODO: handle resizing and scrollback 2209improve? */ 2210 for (iy=-sb_length;iy<sy;iy++) 2211 { 2212 screen_char_t *src,*dst; 2213 ny=iy-sy+nsy; 2214 if (ny<-max_scrollback) 2215 continue; 2216 2217 if (iy<0) 2218 src=&sbuf[sx*(max_scrollback+iy)]; 2219 else 2220 src=&screen[sx*iy]; 2221 2222 if (ny<0) 2223 dst=&nsbuf[nsx*(max_scrollback+ny)]; 2224 else 2225 dst=&nscreen[nsx*ny]; 2226 2227 memcpy(dst,src,copy_sx*sizeof(screen_char_t)); 2228 } 2229 2230 sb_length=sb_length+sy-nsy; 2231 if (sb_length>max_scrollback) 2232 sb_length=max_scrollback; 2233 if (sb_length<0) 2234 sb_length=0; 2235 2236 cursor_y = nsy-(sy-cursor_y); 2237 2238 sx=nsx; 2239 sy=nsy; 2240 free(screen); 2241 free(sbuf); 2242 screen=nscreen; 2243 sbuf=nsbuf; 2244 2245 [self ts_goto: cursor_x : cursor_y]; 2246 2247 [self _updateScroller]; 2248 2249 [tp setTerminalScreenWidth: sx height: sy]; 2250 2251 if (master_fd!=-1) 2252 { 2253 ws.ws_row=nsy; 2254 ws.ws_col=nsx; 2255 ioctl(master_fd,TIOCSWINSZ,&ws); 2256 } 2257 2258 [self setNeedsDisplay: YES]; 2259} 2260 2261-(void) setFrame: (NSRect)frame 2262{ 2263 [super setFrame: frame]; 2264 [self _resizeTerminalTo: frame.size]; 2265} 2266 2267-(void) setFrameSize: (NSSize)size 2268{ 2269 [super setFrameSize: size]; 2270 [self _resizeTerminalTo: size]; 2271} 2272 2273 2274- initWithFrame: (NSRect)frame 2275{ 2276 sx=80; 2277 sy=25; 2278 2279 if (!(self=[super initWithFrame: frame])) return nil; 2280 2281 { 2282 NSSize s; 2283 NSRect r; 2284 2285 font=[TerminalViewDisplayPrefs terminalFont]; 2286 [font retain]; 2287 2288 boldFont=[TerminalViewDisplayPrefs boldTerminalFont]; 2289 [boldFont retain]; 2290 2291 r=[font boundingRectForFont]; 2292 s=[TerminalView characterCellSize]; 2293 fx=s.width; 2294 fy=s.height; 2295 2296 /* TODO: clear up font metrics issues with xlib/backart */ 2297 NSLog(@"NSFont %@ info %@ size %g %@ %d", font, [font fontInfo], [font pointSize], NSStringFromRect([font boundingRectForGlyph: 'A']), [font glyphIsEncoded: 'A']); 2298 fx0=-r.origin.x; 2299 fy0=-r.origin.y; 2300 NSDebugLLog(@"term",@"Bounding (%g %g)+(%g %g)",-fx0,-fy0,fx,fy); 2301 font_encoding=[font mostCompatibleStringEncoding]; 2302 boldFont_encoding=[boldFont mostCompatibleStringEncoding]; 2303 NSDebugLLog(@"term",@"encoding %i and %i", 2304 font_encoding,boldFont_encoding); 2305 } 2306 2307 use_multi_cell_glyphs=[TerminalViewDisplayPrefs useMultiCellGlyphs]; 2308 blackOnWhite=[TerminalViewDisplayPrefs blackOnWhite]; 2309 2310 screen=malloc(sizeof(screen_char_t)*sx*sy); 2311 memset(screen,0,sizeof(screen_char_t)*sx*sy); 2312 draw_all=2; 2313 2314 max_scrollback=[TerminalViewDisplayPrefs scrollBackLines]; 2315 sbuf=malloc(sizeof(screen_char_t)*sx*max_scrollback); 2316 memset(sbuf,0,sizeof(screen_char_t)*sx*max_scrollback); 2317 2318 tp=[[TerminalParser_Linux alloc] initWithTerminalScreen: self 2319 width: sx height: sy]; 2320 2321 master_fd=-1; 2322 2323 [self registerForDraggedTypes: [NSArray arrayWithObjects: 2324 NSFilenamesPboardType,NSStringPboardType,nil]]; 2325 2326 [[NSNotificationCenter defaultCenter] 2327 addObserver: self 2328 selector: @selector(viewPrefsDidChange:) 2329 name: TerminalViewDisplayPrefsDidChangeNotification 2330 object: nil]; 2331 2332 return self; 2333} 2334 2335-(void) dealloc 2336{ 2337 [[NSNotificationCenter defaultCenter] 2338 removeObserver: self]; 2339 2340 [self closeProgram]; 2341 2342 DESTROY(tp); 2343 2344 [scroller setTarget: nil]; 2345 DESTROY(scroller); 2346 2347 free(screen); 2348 free(sbuf); 2349 screen=NULL; 2350 sbuf=NULL; 2351 2352 DESTROY(font); 2353 DESTROY(boldFont); 2354 2355 DESTROY(title_window); 2356 DESTROY(title_miniwindow); 2357 2358 [super dealloc]; 2359} 2360 2361 2362-(NSString *) windowTitle 2363{ 2364 return title_window; 2365} 2366 2367-(NSString *) miniwindowTitle 2368{ 2369 return title_miniwindow; 2370} 2371 2372-(NSString *) representedFilename 2373{ 2374 return title_filename; 2375} 2376 2377 2378-(void) setIgnoreResize: (BOOL)ignore 2379{ 2380 ignore_resize=ignore; 2381} 2382 2383-(void) setBorder: (CGFloat)x : (CGFloat)y 2384{ 2385 border_x=x; 2386 border_y=y; 2387} 2388 2389 2390+(NSSize) characterCellSize 2391{ 2392 NSFont *f=[TerminalViewDisplayPrefs terminalFont]; 2393 NSSize s; 2394 s=[f boundingRectForFont].size; 2395 if ([TerminalViewDisplayPrefs useMultiCellGlyphs]) 2396 { 2397 s.width=[f boundingRectForGlyph: 'A'].size.width; 2398 } 2399 return s; 2400} 2401 2402+(void) registerPasteboardTypes 2403{ 2404 NSArray *types=[NSArray arrayWithObject: NSStringPboardType]; 2405 [NSApp registerServicesMenuSendTypes: types returnTypes: nil]; 2406} 2407 2408@end 2409 2410