2
copyright 2002, 2003 Alexander Malmberg <alexander@malmberg.org>
3
forkpty replacement, 2005-2008 Riccardo Mottola <rmottola@users.sf.net>
5
This file is a part of Terminal.app. Terminal.app is free software; you
6
can redistribute it and/or modify it under the terms of the GNU General
7
Public License as published by the Free Software Foundation; version 2
8
of the License. See COPYING or main.m for more information.
12
TODO: Move pty and child process handling to another class. Make this a
13
stupid but fast character cell display view.
16
/* define this if you need the forkpty replacement and it is not automatically
18
#undef USE_FORKPTY_REPLACEMENT
20
#define USE_FORKPTY_REPLACEMENT 1
22
/* check for solaris */
23
#if defined (__SVR4) && defined (__sun)
25
#define USE_FORKPTY_REPLACEMENT 1
32
# include <sys/types.h>
33
# include <sys/ioctl.h>
36
#define TCSETS TIOCSETA
39
# include <sys/types.h>
40
# include <sys/ioctl.h>
50
#include <sys/types.h>
54
#if !(defined (__NetBSD__)) && !(defined (__SOLARIS__))
59
#include <Foundation/NSBundle.h>
60
#include <Foundation/NSDebug.h>
61
#include <Foundation/NSNotification.h>
62
#include <Foundation/NSRunLoop.h>
63
#include <Foundation/NSUserDefaults.h>
64
#include <Foundation/NSCharacterSet.h>
65
#include <Foundation/NSArchiver.h>
66
#include <GNUstepBase/Unicode.h>
67
#include <AppKit/NSApplication.h>
68
#include <AppKit/NSPasteboard.h>
69
#include <AppKit/NSDragging.h>
70
#include <AppKit/NSEvent.h>
71
#include <AppKit/NSGraphics.h>
72
#include <AppKit/NSScroller.h>
73
#include <AppKit/DPSOperators.h>
75
#include "TerminalView.h"
77
#include "TerminalViewPrefs.h"
80
/* forkpty replacement */
81
#ifdef USE_FORKPTY_REPLACEMENT
82
#include <stdio.h> /* for stderr and perror*/
83
#include <errno.h> /* for int errno */
85
#include <sys/termios.h>
86
#include <sys/types.h>
92
#define PATH_TTY "/dev/tty"
94
int ptyMakeControllingTty(int *slaveFd, const char *slaveName)
99
if (!slaveFd || *slaveFd < 0)
101
perror("slaveFd invalid");
105
/* disconnect from the old controlling tty */
107
if ((fd = open(PATH_TTY, O_RDWR | O_NOCTTY)) >= 0 )
109
ioctl(fd, TIOCNOTTY, NULL);
115
pgid = setsid(); /* create session and set process ID */
119
perror("EPERM error on setsid");
122
/* Make it our controlling tty */
124
if (ioctl(*slaveFd, TIOCSCTTY, NULL) == -1)
127
#warning TIOCSCTTY replacement
129
/* first terminal we open after setsid() is the controlling one */
130
char *controllingTty;
133
controllingTty = ttyname(*slaveFd);
134
ctr_fdes = open(controllingTty, O_RDWR);
137
#endif /* TIOCSCTTY */
139
#if defined (TIOCSPGRP)
140
ioctl (0, TIOCSPGRP, &pgid);
142
#warning no TIOCSPGRP
148
if ((fd = open(slaveName, O_RDWR)) >= 0)
152
printf("Got new filedescriptor...\n");
154
if ((fd = open(PATH_TTY, O_RDWR)) == -1)
162
int openpty(int *amaster, int *aslave, char *name, const struct termios *termp, const struct winsize *winp)
167
fdm = open("/dev/ptmx", O_RDWR); /* open master */
170
perror("openpty:open(master)");
173
if(grantpt(fdm)) /* grant access to the slave */
175
perror("openpty:grantpt(master)");
179
if(unlockpt(fdm)) /* unlock the slave terminal */
181
perror("openpty:unlockpt(master)");
186
slaveName = ptsname(fdm); /* get name of the slave */
187
if (slaveName == NULL)
189
perror("openpty:ptsname(master)");
193
if (name) /* of name ptr not null, copy it name back */
194
strcpy(name, slaveName);
196
fds = open(slaveName, O_RDWR | O_NOCTTY); /* open slave */
199
perror("openpty:open(slave)");
204
/* ldterm and ttcompat are automatically pushed on the stack on some systems*/
206
if (ioctl(fds, I_PUSH, "ptem") == -1) /* pseudo terminal module */
208
perror("openpty:ioctl(I_PUSH, ptem");
213
if (ioctl(fds, I_PUSH, "ldterm") == -1) /* ldterm must stay atop ptem */
215
perror("forkpty:ioctl(I_PUSH, ldterm");
222
/* set terminal parameters if present */
224
ioctl(fds, TCSETS, termp);
226
ioctl(fds, TIOCSWINSZ, winp);
233
int forkpty (int *amaster, char *slaveName, const struct termios *termp, const struct winsize *winp)
235
int fdm, fds; /* master and slave file descriptors */
238
if (openpty(&fdm, &fds, slaveName, termp, winp) == -1)
240
perror("forkpty:openpty()");
249
perror("forkpty:fork()");
256
ptyMakeControllingTty(&fds, slaveName);
257
if (fds != STDIN_FILENO && dup2(fds, STDIN_FILENO) == -1)
258
perror("error duplicationg stdin");
259
if (fds != STDOUT_FILENO && dup2(fds, STDOUT_FILENO) == -1)
260
perror("error duplicationg stdout");
261
if (fds != STDERR_FILENO && dup2(fds, STDERR_FILENO) == -1)
262
perror("error duplicationg stderr");
264
if (fds != STDIN_FILENO && fds != STDOUT_FILENO && fds != STDERR_FILENO)
278
#endif /* forpkty replacement */
281
@interface NSView (unlockfocus)
282
-(void) unlockFocusNeedsFlush: (BOOL)flush;
287
*TerminalViewBecameIdleNotification=@"TerminalViewBecameIdle",
288
*TerminalViewBecameNonIdleNotification=@"TerminalViewBecameNonIdle",
290
*TerminalViewTitleDidChangeNotification=@"TerminalViewTitleDidChange";
294
@interface TerminalView (scrolling)
295
-(void) _updateScroller;
296
-(void) _scrollTo: (int)new_scroll update: (BOOL)update;
297
-(void) setScroller: (NSScroller *)sc;
300
@interface TerminalView (selection)
301
-(void) _clearSelection;
304
@interface TerminalView (input) <RunLoopEvents>
305
-(void) closeProgram;
307
-(void) runProgram: (NSString *)path
308
withArguments: (NSArray *)args
309
initialInput: (NSString *)d;
314
TerminalScreen protocol implementation and rendering methods
317
@implementation TerminalView (display)
319
#define ADD_DIRTY(ax0,ay0,asx,asy) do { \
324
dirty.x1=(ax0)+(asx); \
325
dirty.y1=(ay0)+(asy); \
329
if (dirty.x0>(ax0)) dirty.x0=(ax0); \
330
if (dirty.y0>(ay0)) dirty.y0=(ay0); \
331
if (dirty.x1<(ax0)+(asx)) dirty.x1=(ax0)+(asx); \
332
if (dirty.y1<(ay0)+(asy)) dirty.y1=(ay0)+(asy); \
337
#define SCREEN(x,y) (screen[(y)*sx+(x)])
340
/* handle accumulated pending scrolls with a single composite */
341
-(void) _handlePendingScroll: (BOOL)lockFocus
343
float x0,y0,w,h,dx,dy;
348
if (pending_scroll>=sy || pending_scroll<=-sy)
354
NSDebugLLog(@"draw",@"_handlePendingScroll %i %i",pending_scroll,lockFocus);
359
if (pending_scroll>0)
362
h=(sy-pending_scroll)*fy;
363
dy=pending_scroll*fy;
369
pending_scroll=-pending_scroll;
371
y0=pending_scroll*fy;
372
h=(sy-pending_scroll)*fy;
380
DPScomposite(GSCurrentContext(),border_x+x0,border_y+y0,w,h,
381
[self gState],border_x+dx,border_y+dy,NSCompositeCopy);
383
[self unlockFocusNeedsFlush: NO];
390
static int total_draw=0;
393
static const float col_h[8]={ 0,240,120,180, 0,300, 60, 0};
394
static const float col_s[8]={0.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0};
396
static void set_background(NSGraphicsContext *gc,
397
unsigned char color,unsigned char in)
411
DPSsethsbcolor(gc,bh,bs,bb);
414
static void set_foreground(NSGraphicsContext *gc,
415
unsigned char color,unsigned char in)
445
DPSsethsbcolor(gc,h,s,b);
449
-(void) drawRect: (NSRect)r
452
unsigned char buf[8];
453
NSGraphicsContext *cur=GSCurrentContext();
455
NSFont *f,*current_font=nil;
460
NSDebugLLog(@"draw",@"drawRect: (%g %g)+(%g %g) %i\n",
461
r.origin.x,r.origin.y,r.size.width,r.size.height,
465
[self _handlePendingScroll: NO];
467
/* draw the black border around the view if needed*/
471
if (r.origin.x<border_x)
472
DPSrectfill(cur,r.origin.x,r.origin.y,border_x-r.origin.x,r.size.height);
473
if (r.origin.y<border_y)
474
DPSrectfill(cur,r.origin.x,r.origin.y,r.size.width,border_y-r.origin.y);
477
b=r.origin.x+r.size.width;
479
DPSrectfill(cur,a,r.origin.y,b-a,r.size.height);
481
b=r.origin.y+r.size.height;
483
DPSrectfill(cur,r.origin.x,a,r.size.width,b-a);
486
/* figure out what character cells might need redrawing */
487
r.origin.x-=border_x;
488
r.origin.y-=border_y;
490
x0=floor(r.origin.x/fx);
491
x1=ceil((r.origin.x+r.size.width)/fx);
495
y1=floor(r.origin.y/fy);
496
y0=ceil((r.origin.y+r.size.height)/fy);
502
NSDebugLLog(@"draw",@"dirty (%i %i)-(%i %i)\n",x0,y0,x1,y1);
504
draw_cursor=draw_cursor || draw_all ||
505
(SCREEN(cursor_x,cursor_y).attr&0x80)!=0;
510
float scr_y,scr_x,start_x;
512
/* setting the color is slow, so we try to avoid it */
513
unsigned char l_color,l_attr,color;
515
/* Fill the background of dirty cells. Since the background doesn't
516
change that often, runs of dirty cells with the same background color
517
are combined and drawn with a single rectfill. */
520
set_foreground(cur,l_color,l_attr);
521
for (iy=y0;iy<y1;iy++)
523
ry=iy+current_scroll;
527
ch=&sbuf[x0+(max_scrollback+ry)*sx];
529
scr_y=(sy-1-iy)*fy+border_y;
531
#define R(scr_x,scr_y,fx,fy) \
533
DPSsetgray(cur,0.0); \
534
DPSrectfill(cur,scr_x,scr_y,fx,fy); \
536
DPSrectstroke(cur,scr_x,scr_y,fx,fy); \
539
/* ~400 cycles/cell on average */
540
#define R(scr_x,scr_y,fx,fy) DPSrectfill(cur,scr_x,scr_y,fx,fy)
542
for (ix=x0;ix<x1;ix++,ch++)
544
if (!draw_all && !(ch->attr&0x80))
548
scr_x=ix*fx+border_x;
549
R(start_x,scr_y,scr_x-start_x,fy);
555
scr_x=ix*fx+border_x;
560
if (ch->attr&0x40) color^=0xf;
561
if (color!=l_color || (ch->attr&0x03)!=l_attr)
565
R(start_x,scr_y,scr_x-start_x,fy);
570
l_attr=ch->attr&0x03;
571
set_foreground(cur,l_color,l_attr);
576
color=ch->color&0xf0;
577
if (ch->attr&0x40) color^=0xf0;
582
R(start_x,scr_y,scr_x-start_x,fy);
587
l_attr=ch->attr&0x03;
588
set_background(cur,l_color,l_attr);
598
scr_x=ix*fx+border_x;
599
R(start_x,scr_y,scr_x-start_x,fy);
603
/* now draw any dirty characters */
604
for (iy=y0;iy<y1;iy++)
606
ry=iy+current_scroll;
610
ch=&sbuf[x0+(max_scrollback+ry)*sx];
612
scr_y=(sy-1-iy)*fy+border_y;
614
for (ix=x0;ix<x1;ix++,ch++)
616
if (!draw_all && !(ch->attr&0x80))
621
scr_x=ix*fx+border_x;
623
/* ~1700 cycles/change */
624
if (ch->attr&0x02 || (ch->ch!=0 && ch->ch!=32))
629
if (ch->attr&0x40) color^=0xf;
630
if (color!=l_color || (ch->attr&0x03)!=l_attr)
633
l_attr=ch->attr&0x03;
634
set_foreground(cur,l_color,l_attr);
639
color=ch->color&0xf0;
640
if (ch->attr&0x40) color^=0xf0;
644
l_attr=ch->attr&0x03;
645
set_background(cur,l_color,l_attr);
650
if (ch->ch!=0 && ch->ch!=32 && ch->ch!=MULTI_CELL_GLYPH)
655
encoding=boldFont_encoding;
660
encoding=font_encoding;
665
/* ~190 cycles/change */
670
/* we short-circuit utf8 for performance with back-art */
671
/* TODO: short-circuit latin1 too? */
672
if (encoding==NSUTF8StringEncoding)
677
buf[2]=(uch&0x3f)|0x80;
679
buf[1]=(uch&0x3f)|0x80;
681
buf[0]=(uch&0x0f)|0xe0;
686
buf[1]=(uch&0x3f)|0x80;
688
buf[0]=(uch&0x1f)|0xc0;
707
unsigned char *pbuf=buf;
708
int dlen=sizeof(buf)-1;
709
GSFromUnicode(&pbuf,&dlen,&uch,1,encoding,NULL,GSUniTerminate);
713
DPSmoveto(cur,scr_x+fx0,scr_y+fy0);
714
/* baseline here for mc-case 0.65 */
718
/* ~95 cycles to ARTGState -DPSshow:... */
719
/* ~343 cycles to isEmpty */
720
/* ~593 cycles to currentpoint */
721
/* ~688 cycles to transform */
722
/* ~1152 cycles to FTFont -drawString:... */
723
/* ~1375 cycles to -drawString:... setup */
724
/* ~1968 cycles cmap lookup */
725
/* ~2718 cycles sbit lookup */
726
/* ~~2750 cycles blit setup */
727
/* ~3140 cycles blit loop, empty call */
728
/* ~3140 cycles blit loop, setup */
729
/* ~3325 cycles blit loop, no write */
730
/* ~3800 cycles total */
735
DPSrectfill(cur,scr_x,scr_y,fx,1);
743
[[TerminalViewDisplayPrefs cursorColor] set];
745
x=cursor_x*fx+border_x;
746
y=(sy-1-cursor_y+current_scroll)*fy+border_y;
748
switch ([TerminalViewDisplayPrefs cursorStyle])
751
DPSrectfill(cur,x,y,fx,fy*0.1);
753
case CURSOR_BLOCK_STROKE:
754
DPSrectstroke(cur,x+0.5,y+0.5,fx-1.0,fy-1.0);
756
case CURSOR_BLOCK_FILL:
757
DPSrectfill(cur,x,y,fx,fy);
759
case CURSOR_BLOCK_INVERT:
760
DPScompositerect(cur,x,y,fx,fy,
761
NSCompositeHighlight);
767
NSDebugLLog(@"draw",@"total_draw=%i",total_draw);
777
-(void) setNeedsDisplayInRect: (NSRect)r
780
[super setNeedsDisplayInRect: r];
783
-(void) setNeedsLazyDisplayInRect: (NSRect)r
787
[super setNeedsDisplayInRect: r];
791
-(void) benchmark: (id)sender
795
NSRect r=[self frame];
796
t1=[NSDate timeIntervalSinceReferenceDate];
803
[self unlockFocusNeedsFlush: NO];
805
t2=[NSDate timeIntervalSinceReferenceDate];
807
fprintf(stderr,"%8.4f %8.5f/redraw total_draw=%i\n",t2,t2/i,total_draw);
811
-(void) ts_setTitle: (NSString *)new_title type: (int)title_type
813
NSDebugLLog(@"ts",@"setTitle: %@ type: %i",new_title,title_type);
814
if (title_type==1 || title_type==0)
815
ASSIGN(title_miniwindow,new_title);
816
if (title_type==2 || title_type==0)
817
ASSIGN(title_window,new_title);
818
[[NSNotificationCenter defaultCenter]
819
postNotificationName: TerminalViewTitleDidChangeNotification
824
-(void) ts_goto: (int)x:(int)y
826
NSDebugLLog(@"ts",@"goto: %i:%i",x,y);
829
if (cursor_x>=sx) cursor_x=sx-1;
830
if (cursor_x<0) cursor_x=0;
831
if (cursor_y>=sy) cursor_y=sy-1;
832
if (cursor_y<0) cursor_y=0;
835
-(void) ts_putChar: (screen_char_t)ch count: (int)c at: (int)x:(int)y
840
NSDebugLLog(@"ts",@"putChar: '%c' %02x %02x count: %i at: %i:%i",
841
ch.ch,ch.color,ch.attr,c,x,y);
843
if (y<0 || y>=sy) return;
858
-(void) ts_putChar: (screen_char_t)ch count: (int)c offset: (int)ofs
863
NSDebugLLog(@"ts",@"putChar: '%c' %02x %02x count: %i offset: %i",
864
ch.ch,ch.color,ch.attr,c,ofs);
877
ADD_DIRTY(0,0,sx,sy); /* TODO */
880
-(void) ts_scrollUp: (int)t:(int)b rows: (int)nr save: (BOOL)save
882
screen_char_t *d, *s;
884
NSDebugLLog(@"ts",@"scrollUp: %i:%i rows: %i save: %i",
887
if (save && t==0 && b==sy) /* TODO? */
890
if (nr<max_scrollback)
892
memmove(sbuf,&sbuf[sx*nr],sizeof(screen_char_t)*sx*(max_scrollback-nr));
900
memmove(&sbuf[sx*(max_scrollback-num)],screen,num*sx*sizeof(screen_char_t));
904
memmove(&sbuf[sx*(max_scrollback-num)],screen,sy*sx*sizeof(screen_char_t));
906
/* TODO: should this use video_erase_char? */
907
memset(&sbuf[sx*(max_scrollback-num+sy)],0,sx*(num-sy)*sizeof(screen_char_t));
910
if (sb_length>max_scrollback)
911
sb_length=max_scrollback;
916
if (b > sy || t >= b || nr < 1)
921
if (current_y>=t && current_y<=b)
923
SCREEN(current_x,current_y).attr|=0x80;
926
TODO: does this properly handle the case when the cursor is in
927
an area that gets scrolled 'over'?
929
now it does, but not in an optimal way. handling of this could be
930
optimized in all scrolling methods, but it probably won't make
934
memmove(d, s, (b-t-nr) * sx * sizeof(screen_char_t));
943
float x0,y0,w,h,dx,dy;
946
[self _handlePendingScroll: YES];
957
DPScomposite(GSCurrentContext(),border_x+x0,border_y+y0,w,h,
958
[self gState],border_x+dx,border_y+dy,NSCompositeCopy);
959
[self unlockFocusNeedsFlush: NO];
963
ADD_DIRTY(0,t,sx,b-t);
966
-(void) ts_scrollDown: (int)t:(int)b rows: (int)nr
971
NSDebugLLog(@"ts",@"scrollDown: %i:%i rows: %i",
976
if (b > sy || t >= b || nr < 1)
980
if (current_y>=t && current_y<=b)
982
SCREEN(current_x,current_y).attr|=0x80;
985
memmove(s + step, s, (b-t-nr)*sx*sizeof(screen_char_t));
994
float x0,y0,w,h,dx,dy;
997
[self _handlePendingScroll: YES];
1008
DPScomposite(GSCurrentContext(),border_x+x0,border_y+y0,w,h,
1009
[self gState],border_x+dx,border_y+dy,NSCompositeCopy);
1010
[self unlockFocusNeedsFlush: NO];
1014
ADD_DIRTY(0,t,sx,b-t);
1017
-(void) ts_shiftRow: (int)y at: (int)x0 delta: (int)delta
1019
screen_char_t *s,*d;
1021
NSDebugLLog(@"ts",@"shiftRow: %i at: %i delta: %i",
1024
if (y<0 || y>=sy) return;
1025
if (x0<0 || x0>=sx) return;
1029
SCREEN(current_x,current_y).attr|=0x80;
1045
memmove(d,s,sizeof(screen_char_t)*c);
1046
if (!current_scroll)
1048
float cx0,y0,w,h,dx,dy;
1051
[self _handlePendingScroll: YES];
1064
DPScomposite(GSCurrentContext(),border_x+cx0,border_y+y0,w,h,
1065
[self gState],border_x+dx,border_y+dy,NSCompositeCopy);
1066
[self unlockFocusNeedsFlush: NO];
1069
ADD_DIRTY(0,y,sx,1);
1072
-(screen_char_t) ts_getCharAt: (int)x:(int)y
1074
NSDebugLLog(@"ts",@"getCharAt: %i:%i",x,y);
1079
-(void) addDataToWriteBuffer: (const char *)data
1087
[[NSRunLoop currentRunLoop]
1088
addEvent: (void *)master_fd
1091
forMode: NSDefaultRunLoopMode];
1094
if (write_buf_len+len>write_buf_size)
1096
/* Round up to nearest multiple of 512 bytes. */
1097
write_buf_size=(write_buf_len+len+511)&~511;
1098
write_buf=realloc(write_buf,write_buf_size);
1100
memcpy(&write_buf[write_buf_len],data,len);
1104
-(void) ts_sendCString: (const char *)msg
1106
[self ts_sendCString: msg length: strlen(msg)];
1108
-(void) ts_sendCString: (const char *)msg length: (int)len
1116
[self addDataToWriteBuffer: msg length: len];
1120
l=write(master_fd,msg,len);
1124
NSLog(_(@"Unexpected error while writing: %m."));
1127
[self addDataToWriteBuffer: &msg[l] length: len-l];
1132
-(BOOL) useMultiCellGlyphs
1134
return use_multi_cell_glyphs;
1137
-(int) relativeWidthOfCharacter: (unichar)ch
1140
if (!use_multi_cell_glyphs)
1142
s=ceil([font boundingRectForGlyph: ch].size.width/fx);
1149
-(void) viewPrefsDidChange: (NSNotification *)n
1151
/* TODO: handle font changes? */
1152
[self setNeedsDisplay: YES];
1162
@implementation TerminalView (scrolling)
1164
-(void) _updateScroller
1168
[scroller setEnabled: YES];
1169
[scroller setFloatValue: (current_scroll+sb_length)/(float)(sb_length)
1170
knobProportion: sy/(float)(sy+sb_length)];
1174
[scroller setEnabled: NO];
1178
-(void) _scrollTo: (int)new_scroll update: (BOOL)update
1182
if (new_scroll<-sb_length)
1183
new_scroll=-sb_length;
1185
if (new_scroll==current_scroll)
1187
current_scroll=new_scroll;
1191
[self _updateScroller];
1194
[self setNeedsDisplay: YES];
1197
-(void) scrollWheel: (NSEvent *)e
1199
float delta=[e deltaY];
1203
if ([e modifierFlags]&NSShiftKeyMask)
1205
else if ([e modifierFlags]&NSControlKeyMask)
1210
new_scroll=current_scroll-delta*mult;
1211
[self _scrollTo: new_scroll update: YES];
1214
-(void) _updateScroll: (id)sender
1217
int part=[scroller hitPart];
1220
if (part==NSScrollerKnob ||
1221
part==NSScrollerKnobSlot)
1223
float f=[scroller floatValue];
1224
new_scroll=(f-1.0)*sb_length;
1227
else if (part==NSScrollerDecrementLine)
1228
new_scroll=current_scroll-1;
1229
else if (part==NSScrollerDecrementPage)
1230
new_scroll=current_scroll-sy/2;
1231
else if (part==NSScrollerIncrementLine)
1232
new_scroll=current_scroll+1;
1233
else if (part==NSScrollerIncrementPage)
1234
new_scroll=current_scroll+sy/2;
1238
[self _scrollTo: new_scroll update: update];
1241
-(void) setScroller: (NSScroller *)sc
1243
[scroller setTarget: nil];
1244
ASSIGN(scroller,sc);
1245
[self _updateScroller];
1246
[scroller setTarget: self];
1247
[scroller setAction: @selector(_updateScroll:)];
1257
@implementation TerminalView (keyboard)
1259
-(void) keyDown: (NSEvent *)e
1261
NSString *s=[e charactersIgnoringModifiers];
1263
NSDebugLLog(@"key",@"got key flags=%08x repeat=%i '%@' '%@' %4i %04x %i %04x %i\n",
1264
[e modifierFlags],[e isARepeat],[e characters],[e charactersIgnoringModifiers],[e keyCode],
1265
[[e characters] characterAtIndex: 0],[[e characters] length],
1266
[[e charactersIgnoringModifiers] characterAtIndex: 0],[[e charactersIgnoringModifiers] length]);
1268
if ([s length]==1 && ([e modifierFlags]&NSShiftKeyMask))
1270
unichar ch=[s characterAtIndex: 0];
1271
if (ch==NSPageUpFunctionKey)
1273
[self _scrollTo: current_scroll-sy+1 update: YES];
1276
if (ch==NSPageDownFunctionKey)
1278
[self _scrollTo: current_scroll+sy-1 update: YES];
1283
/* don't check until we get here so we handle scrollback page-up/down
1284
even when the view's idle */
1288
[tp handleKeyEvent: e];
1291
-(BOOL) acceptsFirstResponder
1295
-(BOOL) becomeFirstResponder
1299
-(BOOL) resignFirstResponder
1308
Selection, copy/paste/services
1311
@implementation TerminalView (selection)
1313
-(NSString *) _selectionAsString
1315
int ofs=max_scrollback*sx;
1316
NSMutableString *mstr;
1323
if (selection.length==0)
1326
mstr=[[NSMutableString alloc] init];
1327
j=selection.location+selection.length;
1329
for (i=selection.location;i<j;i++)
1339
if (ch!=' ' && ch!=0 && ch!=MULTI_CELL_GLYPH)
1348
ws_len=0; /* make sure we break out of the outer loop */
1353
tmp=[[NSString alloc] initWithCharacters: buf length: len];
1354
[mstr appendString: tmp];
1358
[mstr appendString: @"\n"];
1366
for (;i<j && ws_len;i++,ws_len--)
1371
tmp=[[NSString alloc] initWithCharacters: buf length: 32];
1372
[mstr appendString: tmp];
1383
tmp=[[NSString alloc] initWithCharacters: buf length: 32];
1384
[mstr appendString: tmp];
1392
tmp=[[NSString alloc] initWithCharacters: buf length: len];
1393
[mstr appendString: tmp];
1397
return AUTORELEASE(mstr);
1401
-(void) _setSelection: (struct selection_range)s
1405
if (s.location<-sb_length*sx)
1407
s.length+=sb_length*sx+s.location;
1408
s.location=-sb_length*sx;
1410
if (s.location+s.length>sx*sy)
1412
s.length=sx*sy-s.location;
1415
if (!s.length && !selection.length)
1417
if (s.length==selection.length && s.location==selection.location)
1420
ofs2=max_scrollback*sx;
1422
j=selection.location+selection.length;
1426
for (i=selection.location;i<j && i<0;i++)
1428
sbuf[ofs2+i].attr&=0xbf;
1429
sbuf[ofs2+i].attr|=0x80;
1433
screen[i].attr&=0xbf;
1434
screen[i].attr|=0x80;
1437
i=s.location+s.length;
1438
if (i<selection.location)
1439
i=selection.location;
1440
j=selection.location+selection.length;
1441
for (;i<j && i<0;i++)
1443
sbuf[ofs2+i].attr&=0xbf;
1444
sbuf[ofs2+i].attr|=0x80;
1448
screen[i].attr&=0xbf;
1449
screen[i].attr|=0x80;
1453
j=s.location+s.length;
1454
for (;i<j && i<0;i++)
1456
if (!(sbuf[ofs2+i].attr&0x40))
1457
sbuf[ofs2+i].attr|=0xc0;
1461
if (!(screen[i].attr&0x40))
1462
screen[i].attr|=0xc0;
1466
[self setNeedsLazyDisplayInRect: [self bounds]];
1469
-(void) _clearSelection
1471
struct selection_range s;
1472
s.location=s.length=0;
1473
[self _setSelection: s];
1477
-(void) copy: (id)sender
1479
NSPasteboard *pb=[NSPasteboard generalPasteboard];
1480
NSString *s=[self _selectionAsString];
1486
[pb declareTypes: [NSArray arrayWithObject: NSStringPboardType]
1488
[pb setString: s forType: NSStringPboardType];
1491
-(void) paste: (id)sender
1493
NSPasteboard *pb=[NSPasteboard generalPasteboard];
1497
type=[pb availableTypeFromArray: [NSArray arrayWithObject: NSStringPboardType]];
1500
str=[pb stringForType: NSStringPboardType];
1502
[tp sendString: str];
1505
-(BOOL) writeSelectionToPasteboard: (NSPasteboard *)pb
1511
s=[self _selectionAsString];
1518
[pb declareTypes: t owner: self];
1519
for (i=0;i<[t count];i++)
1521
if ([[t objectAtIndex: i] isEqual: NSStringPboardType])
1524
forType: NSStringPboardType];
1531
-(BOOL) readSelectionFromPasteboard: (NSPasteboard *)pb
1532
{ /* TODO: is it really necessary to implement this? */
1536
-(id) validRequestorForSendType: (NSString *)st
1537
returnType: (NSString *)rt
1539
if (!selection.length)
1541
if (st!=nil && ![st isEqual: NSStringPboardType])
1549
/* Return the range we should select for the given position and granularity:
1554
-(struct selection_range) _selectionRangeAt: (int)pos granularity: (int)g
1556
struct selection_range s;
1559
{ /* select lines */
1560
int l=floor(pos/(float)sx);
1567
{ /* select words */
1568
int ofs=max_scrollback*sx;
1574
ch=sbuf[ofs+pos].ch;
1579
/* try to find a character set for this character */
1580
cs=[NSCharacterSet alphanumericCharacterSet];
1581
if (![cs characterIsMember: ch])
1582
cs=[NSCharacterSet punctuationCharacterSet];
1583
if (![cs characterIsMember: ch])
1584
cs=[NSCharacterSet whitespaceCharacterSet];
1585
if (![cs characterIsMember: ch])
1592
/* search the line backwards for a boundary */
1593
j=floor(pos/(float)sx);
1595
for (i=pos-1;i>=j;i--)
1601
if (ch2==0) ch2=' ';
1603
if (![cs characterIsMember: ch2])
1608
/* and forwards... */
1610
for (i=pos+1;i<j;i++)
1616
if (ch2==0) ch2=' ';
1618
if (![cs characterIsMember: ch2])
1621
s.length=i-s.location;
1631
-(void) mouseDown: (NSEvent *)e
1633
int ofs0,ofs1,first;
1635
struct selection_range s;
1637
struct selection_range r0,r1;
1640
ofs0=0; /* get compiler to shut up */
1642
while ([e type]!=NSLeftMouseUp)
1644
p=[e locationInWindow];
1646
p=[self convertPoint: p fromView: nil];
1647
p.x=floor((p.x-border_x)/fx);
1649
if (p.x>=sx) p.x=sx-1;
1650
p.y=ceil((p.y-border_y)/fy);
1653
p.y=sy-p.y+current_scroll;
1654
ofs1=((int)p.x)+((int)p.y)*sx;
1656
r1=[self _selectionRangeAt: ofs1 granularity: g];
1664
NSDebugLLog(@"select",@"ofs %i %i (%i+%i) (%i+%i)\n",
1666
r0.location,r0.length,
1667
r1.location,r1.length);
1671
s.location=r0.location;
1672
s.length=r1.location+r1.length-r0.location;
1676
s.location=r1.location;
1677
s.length=r0.location+r0.length-r1.location;
1680
[self _setSelection: s];
1681
[self displayIfNeeded];
1683
e=[NSApp nextEventMatchingMask: NSLeftMouseDownMask|NSLeftMouseUpMask|
1684
NSLeftMouseDraggedMask|NSMouseMovedMask
1685
untilDate: [NSDate distantFuture]
1686
inMode: NSEventTrackingRunLoopMode
1690
if (selection.length)
1692
[self writeSelectionToPasteboard: [NSPasteboard pasteboardWithName: @"Selection"]
1693
types: [NSArray arrayWithObject: NSStringPboardType]];
1697
-(void) otherMouseUp: (NSEvent *)e
1699
NSPasteboard *pb=[NSPasteboard pasteboardWithName: @"Selection"];
1703
type=[pb availableTypeFromArray: [NSArray arrayWithObject: NSStringPboardType]];
1706
str=[pb stringForType: NSStringPboardType];
1708
[tp sendString: str];
1718
@implementation TerminalView (input)
1720
-(NSDate *) timedOutEvent: (void *)data type: (RunLoopEventType)t
1721
forMode: (NSString *)mode
1723
NSLog(@"timedOutEvent:type:forMode: ignored");
1741
[self _clearSelection]; /* TODO? */
1743
NSDebugLLog(@"term",@"receiving output");
1747
size=read(master_fd,buf,sizeof(buf));
1748
if (size<0 && errno==EAGAIN)
1757
[self closeProgram];
1759
msg=_(@"[Process exited]");
1763
ch=[msg characterAtIndex: i];
1764
if (ch<256) /* TODO */
1765
[tp processByte: ch];
1767
[tp processByte: '\n'];
1768
[tp processByte: '\r'];
1770
/* Sending this notification might cause us to be deallocated, in
1771
which case we can't let the rest of code here run (and we'd rather
1772
not to avoid a pointless update of the screen). To detect this, we
1773
retain ourself before the call and check the retaincount after. */
1775
[[NSNotificationCenter defaultCenter]
1776
postNotificationName: TerminalViewBecameIdleNotification
1778
if ([self retainCount]==1)
1779
{ /* we only have our own retain left, so we release ourself
1780
(causing us to be deallocated) and return */
1790
for (i=0;i<size;i++)
1791
[tp processByte: buf[i]];
1795
Don't get stuck processing input forever; give other terminal windows
1796
and the user a chance to do things. The numbers affect latency versus
1797
throughput. High numbers means more input is processed before the
1798
screen is updated, leading to higher throughput but also to more
1799
'jerky' updates. Low numbers would give smoother updating and less
1800
latency, but throughput goes down.
1802
TODO: tweak more? seems pretty good now
1804
if (total>=8192 || (num_scrolls+abs(pending_scroll))>10)
1808
if (cursor_x!=current_x || cursor_y!=current_y)
1810
ADD_DIRTY(current_x,current_y,1,1);
1811
SCREEN(current_x,current_y).attr|=0x80;
1812
ADD_DIRTY(cursor_x,cursor_y,1,1);
1816
NSDebugLLog(@"term",@"done (%i %i) (%i %i)\n",
1817
dirty.x0,dirty.y0,dirty.x1,dirty.y1);
1823
// NSLog(@"dirty=(%i %i)-(%i %i)\n",dirty.x0,dirty.y0,dirty.x1,dirty.y1);
1824
dr.origin.x=dirty.x0*fx;
1825
dr.origin.y=dirty.y0*fy;
1826
dr.size.width=(dirty.x1-dirty.x0)*fx;
1827
dr.size.height=(dirty.y1-dirty.y0)*fy;
1828
dr.origin.y=fy*sy-(dr.origin.y+dr.size.height);
1829
// NSLog(@"-> dirty=(%g %g)+(%g %g)\n",dirty.origin.x,dirty.origin.y,dirty.size.width,dirty.size.height);
1830
dr.origin.x+=border_x;
1831
dr.origin.y+=border_y;
1832
[self setNeedsLazyDisplayInRect: dr];
1834
if (current_scroll!=0)
1837
[self setNeedsDisplay: YES];
1840
[self _updateScroller];
1844
-(void) writePendingData
1847
l=write(master_fd,write_buf,write_buf_len);
1851
NSLog(_(@"Unexpected error while writing: %m."));
1854
memmove(write_buf,&write_buf[l],write_buf_len-l);
1857
/* If less than half the buffer is empty, reallocate it, but never free
1859
new_size=(write_buf_len+511)&~511;
1862
if (new_size<=write_buf_size/2)
1864
write_buf_size=new_size;
1865
write_buf=realloc(write_buf,write_buf_size);
1870
[[NSRunLoop currentRunLoop] removeEvent: (void *)master_fd
1872
forMode: NSDefaultRunLoopMode
1877
-(void) receivedEvent: (void *)data
1878
type: (RunLoopEventType)type
1879
extra: (void *)extra
1880
forMode: (NSString *)mode
1883
[self writePendingData];
1884
else if (type==ET_RDESC)
1889
-(void) closeProgram
1893
NSDebugLLog(@"pty",@"closing master fd=%i\n",master_fd);
1894
[[NSRunLoop currentRunLoop] removeEvent: (void *)master_fd
1896
forMode: NSDefaultRunLoopMode
1898
[[NSRunLoop currentRunLoop] removeEvent: (void *)master_fd
1900
forMode: NSDefaultRunLoopMode
1902
write_buf_len=write_buf_size=0;
1910
-(void) runProgram: (NSString *)path
1911
withArguments: (NSArray *)args
1912
inDirectory: (NSString *)directory
1913
initialInput: (NSString *)d
1914
arg0: (NSString *)arg0
1920
const char *cargs[[args count]+2];
1921
const char *cdirectory;
1926
NSDebugLLog(@"pty",@"-runProgram: %@ withArguments: %@ initialInput: %@",
1929
[self closeProgram];
1931
cpath=[path cString];
1933
cargs[0]=[arg0 cString];
1936
cdirectory=[directory cString];
1937
for (i=0;i<[args count];i++)
1939
cargs[i+1]=[[args objectAtIndex: i] cString];
1947
NSLog(_(@"Unable to open pipe for input: %m."));
1950
NSDebugLLog(@"pty",@"creating pipe for initial data, got %i %i",
1951
pipefd[0],pipefd[1]);
1956
ret=forkpty(&master_fd,NULL,NULL,&ws);
1959
NSLog(_(@"Unable to fork: %m."));
1973
putenv("TERM=linux");
1974
putenv("TERM_PROGRAM=GNUstep_Terminal");
1975
execv(cpath,(char *const*)cargs);
1976
fprintf(stderr,"Unable to spawn process '%s': %m!",cpath);
1980
NSDebugLLog(@"pty",@"forked child %i, fd %i",ret,master_fd);
1982
/* Set non-blocking mode for the descriptor. */
1983
flags=fcntl(master_fd,F_GETFL,0);
1986
NSLog(_(@"Unable to set non-blocking mode: %m."));
1991
fcntl(master_fd,F_SETFL,flags);
1994
rl=[NSRunLoop currentRunLoop];
1995
[rl addEvent: (void *)master_fd
1998
forMode: NSDefaultRunLoopMode];
2000
[[NSNotificationCenter defaultCenter]
2001
postNotificationName: TerminalViewBecameNonIdleNotification
2006
const char *s=[d UTF8String];
2008
write(pipefd[1],s,strlen(s));
2012
DESTROY(title_window);
2014
title_window=[[NSString stringWithFormat: @"%@ %@",
2015
path,[args componentsJoinedByString: @" "]] retain];
2017
title_window=[path copy];
2019
ASSIGN(title_miniwindow,path);
2020
[[NSNotificationCenter defaultCenter]
2021
postNotificationName: TerminalViewTitleDidChangeNotification
2025
-(void) runProgram: (NSString *)path
2026
withArguments: (NSArray *)args
2027
initialInput: (NSString *)d
2029
[self runProgram: path
2041
path=[TerminalViewShellPrefs shell];
2042
if ([TerminalViewShellPrefs loginShell])
2043
arg0=[@"-" stringByAppendingString: path];
2046
[self runProgram: path
2060
@implementation TerminalView (drag_n_drop)
2062
static int handled_mask=
2063
NSDragOperationCopy|NSDragOperationPrivate|NSDragOperationGeneric;
2065
-(unsigned int) draggingEntered: (id<NSDraggingInfo>)sender
2067
NSArray *types=[[sender draggingPasteboard] types];
2068
unsigned int mask=[sender draggingSourceOperationMask];
2070
NSDebugLLog(@"dragndrop",@"TerminalView draggingEntered mask=%x types=%@",mask,types);
2072
if (mask&handled_mask &&
2073
([types containsObject: NSFilenamesPboardType] ||
2074
[types containsObject: NSStringPboardType]))
2075
return NSDragOperationCopy;
2079
/* TODO: should I really have to implement this? */
2080
-(BOOL) prepareForDragOperation: (id<NSDraggingInfo>)sender
2082
NSDebugLLog(@"dragndrop",@"preparing for drag");
2086
-(BOOL) performDragOperation: (id<NSDraggingInfo>)sender
2088
NSPasteboard *pb=[sender draggingPasteboard];
2089
NSArray *types=[pb types];
2090
unsigned int mask=[sender draggingSourceOperationMask];
2092
NSDebugLLog(@"dragndrop",@"performDrag %x %@",mask,types);
2094
if (!(mask&handled_mask))
2097
if ([types containsObject: NSFilenamesPboardType])
2102
data=[pb propertyListForType: NSFilenamesPboardType];
2104
data=[NSUnarchiver unarchiveObjectWithData: [pb dataForType: NSFilenamesPboardType]];
2110
[tp sendString: @" "];
2111
[tp sendString: [data objectAtIndex: i]];
2116
if ([types containsObject: NSStringPboardType])
2118
NSString *str=[pb stringForType: NSStringPboardType];
2119
[tp sendString: str];
2133
@implementation TerminalView
2135
-(void) _resizeTerminalTo: (NSSize)size
2139
screen_char_t *nscreen,*nsbuf;
2143
nsx=(size.width-border_x)/fx;
2144
nsy=(size.height-border_y)/fy;
2146
NSDebugLLog(@"term",@"_resizeTerminalTo: (%g %g) %i %i (%g %g)\n",
2147
size.width,size.height,
2153
NSDebugLLog(@"term",@"ignored");
2160
if (nsx==sx && nsy==sy)
2162
/* Do a complete redraw anyway. Even though we don't really need it,
2163
the resize might have caused other things to overwrite our part of the
2169
[self _clearSelection]; /* TODO? */
2171
nscreen=malloc(nsx*nsy*sizeof(screen_char_t));
2172
nsbuf=malloc(nsx*max_scrollback*sizeof(screen_char_t));
2173
if (!nscreen || !nsbuf)
2175
NSLog(@"Failed to allocate screen buffer!");
2178
memset(nscreen,0,sizeof(screen_char_t)*nsx*nsy);
2179
memset(nsbuf,0,sizeof(screen_char_t)*nsx*max_scrollback);
2185
// NSLog(@"copy %i+%i %i (%ix%i)-(%ix%i)\n",start,num,copy_sx,sx,sy,nsx,nsy);
2187
/* TODO: handle resizing and scrollback
2189
for (iy=-sb_length;iy<sy;iy++)
2191
screen_char_t *src,*dst;
2193
if (ny<-max_scrollback)
2197
src=&sbuf[sx*(max_scrollback+iy)];
2202
dst=&nsbuf[nsx*(max_scrollback+ny)];
2204
dst=&nscreen[nsx*ny];
2206
memcpy(dst,src,copy_sx*sizeof(screen_char_t));
2209
sb_length=sb_length+sy-nsy;
2210
if (sb_length>max_scrollback)
2211
sb_length=max_scrollback;
2222
if (cursor_x>sx) cursor_x=sx-1;
2223
if (cursor_y>sy) cursor_y=sy-1;
2225
[self _updateScroller];
2227
[tp setTerminalScreenWidth: sx height: sy];
2233
ioctl(master_fd,TIOCSWINSZ,&ws);
2236
[self setNeedsDisplay: YES];
2239
-(void) setFrame: (NSRect)frame
2241
[super setFrame: frame];
2242
[self _resizeTerminalTo: frame.size];
2245
-(void) setFrameSize: (NSSize)size
2247
[super setFrameSize: size];
2248
[self _resizeTerminalTo: size];
2252
- initWithFrame: (NSRect)frame
2257
if (!(self=[super initWithFrame: frame])) return nil;
2263
font=[TerminalViewDisplayPrefs terminalFont];
2266
boldFont=[TerminalViewDisplayPrefs boldTerminalFont];
2269
r=[font boundingRectForFont];
2270
s=[TerminalView characterCellSize];
2274
/* TODO: clear up font metrics issues with xlib/backart */
2275
NSLog(@"NSFont %@ info %@ size %g %@ %d", font, [font fontInfo], [font pointSize], NSStringFromRect([font boundingRectForGlyph: 'A']), [font glyphIsEncoded: 'A']);
2278
NSDebugLLog(@"term",@"Bounding (%g %g)+(%g %g)",-fx0,-fy0,fx,fy);
2279
font_encoding=[font mostCompatibleStringEncoding];
2280
boldFont_encoding=[boldFont mostCompatibleStringEncoding];
2281
NSDebugLLog(@"term",@"encoding %i and %i",
2282
font_encoding,boldFont_encoding);
2285
use_multi_cell_glyphs=[TerminalViewDisplayPrefs useMultiCellGlyphs];
2287
screen=malloc(sizeof(screen_char_t)*sx*sy);
2288
memset(screen,0,sizeof(screen_char_t)*sx*sy);
2291
max_scrollback=[TerminalViewDisplayPrefs scrollBackLines];
2292
sbuf=malloc(sizeof(screen_char_t)*sx*max_scrollback);
2293
memset(sbuf,0,sizeof(screen_char_t)*sx*max_scrollback);
2295
tp=[[TerminalParser_Linux alloc] initWithTerminalScreen: self
2296
width: sx height: sy];
2300
[self registerForDraggedTypes: [NSArray arrayWithObjects:
2301
NSFilenamesPboardType,NSStringPboardType,nil]];
2303
[[NSNotificationCenter defaultCenter]
2305
selector: @selector(viewPrefsDidChange:)
2306
name: TerminalViewDisplayPrefsDidChangeNotification
2314
[[NSNotificationCenter defaultCenter]
2315
removeObserver: self];
2317
[self closeProgram];
2321
[scroller setTarget: nil];
2332
DESTROY(title_window);
2333
DESTROY(title_miniwindow);
2339
-(NSString *) windowTitle
2341
return title_window;
2344
-(NSString *) miniwindowTitle
2346
return title_miniwindow;
2350
-(void) setIgnoreResize: (BOOL)ignore
2352
ignore_resize=ignore;
2355
-(void) setBorder: (float)x : (float)y
2362
+(NSSize) characterCellSize
2364
NSFont *f=[TerminalViewDisplayPrefs terminalFont];
2366
s=[f boundingRectForFont].size;
2367
if ([TerminalViewDisplayPrefs useMultiCellGlyphs])
2369
s.width=[f boundingRectForGlyph: 'A'].size.width;
2374
+(void) registerPasteboardTypes
2376
NSArray *types=[NSArray arrayWithObject: NSStringPboardType];
2377
[NSApp registerServicesMenuSendTypes: types returnTypes: nil];