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