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