PageRenderTime 168ms CodeModel.GetById 14ms app.highlight 144ms RepoModel.GetById 1ms app.codeStats 0ms

/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