PageRenderTime 61ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/System/Applications/Terminal/TerminalView.m

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