1/* 2 Copyright 2002, 2003 Alexander Malmberg <alexander@malmberg.org> 3 2016 Riccardo Mottola 4 2016 Tim Sheridan 5 6 This file is a part of Terminal.app. Terminal.app is free software; you 7 can redistribute it and/or modify it under the terms of the GNU General 8 Public License as published by the Free Software Foundation; version 2 9 of the License. See COPYING or main.m for more information. 10*/ 11 12#include <math.h> 13#include <sys/wait.h> 14 15#import <Foundation/NSBundle.h> 16#import <Foundation/NSDebug.h> 17#import <Foundation/NSNotification.h> 18#import <Foundation/NSString.h> 19#import <Foundation/NSUserDefaults.h> 20#import <AppKit/NSApplication.h> 21#import <AppKit/NSScroller.h> 22#import <AppKit/NSTabViewItem.h> 23#import <AppKit/NSWindow.h> 24#import <GNUstepGUI/GSHbox.h> 25 26#import "TerminalWindow.h" 27 28#import "TerminalWindowPrefs.h" 29#import "TerminalView.h" 30 31 32/* TODO: this needs cleaning up. chances are this will interfere 33with NSTask */ 34static void get_zombies(void) 35{ 36 int status,pid; 37 while ((pid=waitpid(-1,&status,WNOHANG))>0) 38 { 39// printf("got %i\n",pid); 40 } 41} 42 43 44static int num_instances; 45 46 47NSString *TerminalWindowNoMoreActiveWindowsNotification= 48 @"TerminalWindowNoMoreActiveWindowsNotification"; 49 50 51@implementation TerminalWindowController 52 53- init 54{ 55 if ((self = [super init])) 56 { 57 NSWindow *win; 58 NSScroller *scroller; 59 GSHbox *hb; 60 CGFloat fx,fy; 61 CGFloat scroller_width; 62 NSRect contentRect,windowRect; 63 NSSize contentSize,minSize; 64 NSTabViewItem *tab_item; 65 TerminalView *tv; 66 int sx,sy; 67 68 isShowingTabs = NO; 69 if ([self showTabBar]) 70 isShowingTabs = YES; 71 72 { 73 NSSize size=[TerminalView characterCellSize]; 74 fx=size.width; 75 fy=size.height; 76 } 77 78 sx=[TerminalWindowPrefs defaultWindowWidth]; 79 sy=[TerminalWindowPrefs defaultWindowHeight]; 80 81 scroller_width=[NSScroller scrollerWidth]; 82 83 // calc the rects for our window 84 contentSize = NSMakeSize (fx * sx + scroller_width + 1, fy * sy + 1); 85 minSize = NSMakeSize (fx * 20 + scroller_width + 1, fy * 4 + 1); 86 87 // add the borders to the size 88 contentSize.width += 8; 89 minSize.width += 8; 90 if ([TerminalWindowPrefs addYBorders]) 91 { 92 contentSize.height += 8; 93 minSize.height += 8; 94 } 95 96 contentRect = NSMakeRect (100, 100, contentSize.width, contentSize.height); 97 98 win=[[NSWindow alloc] initWithContentRect: contentRect 99 styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask 100 backing: NSBackingStoreRetained 101 defer: YES]; 102 if (!(self=[super initWithWindow: win])) return nil; 103 104 num_instances++; 105 106 windowRect = [win frame]; 107 minSize.width += windowRect.size.width - contentSize.width; 108 minSize.height += windowRect.size.height - contentSize.height; 109 110 [win setTitle: @"Terminal"]; 111 [win setDelegate: self]; 112 113 [win setContentSize: contentSize]; 114 [win setResizeIncrements: NSMakeSize (fx , fy)]; 115 [win setMinSize: minSize]; 116 117 hb=[[GSHbox alloc] init]; 118 119 scroller=[[NSScroller alloc] initWithFrame: NSMakeRect(0,0,[NSScroller scrollerWidth],fy)]; 120 [scroller setArrowsPosition: NSScrollerArrowsMaxEnd]; 121 [scroller setEnabled: YES]; 122 [scroller setAutoresizingMask: NSViewHeightSizable]; 123 [hb addView: scroller enablingXResizing: NO]; 124 [scroller release]; 125 126 tab_view = [[NSTabView alloc] init]; 127 [tab_view setDelegate:self]; 128 tab_item = [[NSTabViewItem alloc] init]; 129 [tab_item setLabel:@"Terminal"]; 130 if (isShowingTabs) 131 [tab_item setView:hb]; 132 [tab_view addTabViewItem:tab_item]; 133 [tab_item release]; 134 135 tv = [[TerminalView alloc] init]; 136 [tv setIgnoreResize: YES]; 137 [tv setAutoresizingMask: NSViewHeightSizable|NSViewWidthSizable]; 138 [tv setScroller: scroller]; 139 [hb addView: tv]; 140 [tv release]; 141 [win makeFirstResponder: tv]; 142 [tv setIgnoreResize: NO]; 143 144 terminal_views = [[NSMutableArray alloc] init]; 145 [terminal_views addObject:tv]; 146 147 if ([TerminalWindowPrefs addYBorders]) 148 [tv setBorder: 4 : 4]; 149 else 150 [tv setBorder: 4 : 0]; 151 152 if (isShowingTabs) 153 { 154 [win setContentView: tab_view]; 155 } 156 else 157 { 158 [win setContentView: hb]; 159 } 160 DESTROY(hb); 161 162 163 [win release]; 164 165 [[NSNotificationCenter defaultCenter] 166 addObserver: self 167 selector: @selector(_becameIdle:) 168 name: TerminalViewBecameIdleNotification 169 object: tv]; 170 [[NSNotificationCenter defaultCenter] 171 addObserver: self 172 selector: @selector(_becameNonIdle:) 173 name: TerminalViewBecameNonIdleNotification 174 object: tv]; 175 [[NSNotificationCenter defaultCenter] 176 addObserver: self 177 selector: @selector(_updateTitle:) 178 name: TerminalViewTitleDidChangeNotification 179 object: tv]; 180 181 } 182 return self; 183} 184 185 186-(void) _updateTitleFromTerminalView: (TerminalView *)tv 187{ 188 NSUInteger index; 189 190 index = [terminal_views indexOfObjectIdenticalTo:tv]; 191 if (index == NSNotFound) 192 { 193 NSLog(@"updateTitle view not found: %@ %@", [tv windowTitle], [tv representedFilename]); 194 NSLog(@"view is: %@, views are: %@", tv, terminal_views); 195 return; 196 } 197 [[tab_view tabViewItemAtIndex:index] setLabel:[tv windowTitle]]; 198 [tab_view display]; 199 200 if (tv == [self frontTerminalView]) { 201 [[self window] setTitle: [tv windowTitle]]; 202 [[self window] setMiniwindowTitle: [tv miniwindowTitle]]; 203 [[self window] setRepresentedFilename: [tv representedFilename]]; 204 } 205} 206 207-(void) _updateTitle: (NSNotification *)n 208{ 209 TerminalView *tv = [n object]; 210 [self _updateTitleFromTerminalView:tv]; 211} 212 213 214-(void) dealloc 215{ 216 num_instances--; 217 [TerminalWindowController checkActiveWindows]; 218 [[NSNotificationCenter defaultCenter] 219 removeObserver: self]; 220 [terminal_views release]; 221 [super dealloc]; 222} 223 224 225static NSMutableArray *idle_list; 226 227-(void) windowWillClose: (NSNotification *)n 228{ 229 get_zombies(); 230 [idle_list removeObject: self]; 231 [self autorelease]; 232} 233 234-(void) _becameIdle: (NSNotification *)n 235{ 236 NSDebugLLog(@"idle",@"%@ _becameIdle",self); 237 238 if (close_on_idle) 239 { 240 TerminalView *tv = [n object]; 241 [self closeTerminalTab:tv inWindow:[self window]]; 242 return; 243 } 244 245 [idle_list addObject: self]; 246 NSDebugLLog(@"idle",@"idle list: %@",idle_list); 247 248 { 249 NSString *t; 250 251 t=[[self window] title]; 252 t=[t stringByAppendingString: _(@" (idle)")]; 253 [[self window] setTitle: t]; 254 255 t=[[self window] miniwindowTitle]; 256 t=[t stringByAppendingString: _(@" (idle)")]; 257 [[self window] setMiniwindowTitle: t]; 258 } 259 260 [TerminalWindowController checkActiveWindows]; 261} 262 263-(void) _becameNonIdle: (NSNotification *)n 264{ 265 NSDebugLLog(@"idle",@"%@ _becameNonIdle",self); 266 [idle_list removeObject: self]; 267 NSDebugLLog(@"idle",@"idle list: %@",idle_list); 268} 269 270 271-(TerminalView *) frontTerminalView 272{ 273 NSTabViewItem *item = [tab_view selectedTabViewItem]; 274 NSInteger index = [tab_view indexOfTabViewItem:item]; 275 return [terminal_views objectAtIndex:index]; 276} 277 278-(void) setShouldCloseWhenIdle: (BOOL)should 279{ 280 close_on_idle=should; 281} 282 283 284+(void) initialize 285{ 286 if (!idle_list) 287 idle_list=[[NSMutableArray alloc] init]; 288} 289 290 291+(TerminalWindowController *) newTerminalWindow 292{ 293 TerminalWindowController *twc; 294 295 twc=[[self alloc] init]; 296 if ([TerminalWindowPrefs windowCloseBehavior]==0) 297 [twc setShouldCloseWhenIdle: YES]; 298 [twc showWindow: self]; 299 return twc; 300} 301 302+(TerminalWindowController *) idleTerminalWindow 303{ 304 TerminalWindowController *new; 305 306 NSDebugLLog(@"idle",@"get idle window from idle list: %@",idle_list); 307 if ([idle_list count]) 308 return [idle_list objectAtIndex: 0]; 309 new=[[self alloc] init]; 310 [new showWindow: self]; 311 return new; 312} 313 314+(int) numberOfActiveWindows 315{ 316 return num_instances-[idle_list count]; 317} 318 319+(void) checkActiveWindows 320{ 321 if (![self numberOfActiveWindows]) 322 { 323 [[NSNotificationCenter defaultCenter] 324 postNotificationName: TerminalWindowNoMoreActiveWindowsNotification 325 object: self]; 326 } 327} 328 329-(BOOL) showTabBar 330{ 331 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 332 return [defaults boolForKey:@"ShowTabBar"]; 333} 334 335-(void) setShowTabBar:(BOOL)visible inWindow:(NSWindow *)window 336{ 337 // TODO more explicit default value 338 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 339 [defaults setBool:visible forKey:@"ShowTabBar"]; 340 [defaults synchronize]; 341 342 if ([tab_view numberOfTabViewItems] == 1) { 343 if (visible) { 344 NSView *view; 345 346 view = [window contentView]; 347 [view retain]; 348 [window setContentView:tab_view]; 349 [[tab_view tabViewItemAtIndex:0] setView:view]; 350 [tab_view selectFirstTabViewItem:nil]; 351 [tab_view display]; 352 [view release]; 353 isShowingTabs = YES; 354 } else { 355 NSView *view = [[tab_view selectedTabViewItem] view]; 356 [window setContentView:view]; 357 isShowingTabs = NO; 358 } 359 } 360} 361 362-(void) newTerminalTabInWindow:(NSWindow *)window 363{ 364 NSTabViewItem *tab_item; 365 TerminalView *tv; 366 NSScroller *scroller; 367 GSHbox *hb; 368 CGFloat fy; 369 370 if (!isShowingTabs) 371 { 372 NSView *view; 373 374 view = [window contentView]; 375 [view retain]; 376 [window setContentView:tab_view]; 377 [[tab_view tabViewItemAtIndex:0] setView:view]; 378 [view release]; 379 isShowingTabs = YES; 380 } 381 382 { 383 NSSize size=[TerminalView characterCellSize]; 384 fy=size.height; 385 } 386 387 tab_item = [[NSTabViewItem alloc] init]; 388 [tab_item setLabel:@"Terminal"]; 389 [tab_view addTabViewItem:tab_item]; 390 [tab_item release]; 391 392 hb=[[GSHbox alloc] init]; 393 394 scroller=[[NSScroller alloc] initWithFrame: NSMakeRect(0,0,[NSScroller scrollerWidth],fy)]; 395 [scroller setArrowsPosition: NSScrollerArrowsMaxEnd]; 396 [scroller setEnabled: YES]; 397 [scroller setAutoresizingMask: NSViewHeightSizable]; 398 [hb addView: scroller enablingXResizing: NO]; 399 [scroller release]; 400 401 tv = [[TerminalView alloc] init]; 402 [tv setIgnoreResize: YES]; 403 [tv setAutoresizingMask: NSViewHeightSizable|NSViewWidthSizable]; 404 [tv setScroller: scroller]; 405 [hb addView: tv]; 406 [tv release]; 407 [tv setIgnoreResize: NO]; 408 409 [terminal_views addObject:tv]; 410 411 if ([TerminalWindowPrefs addYBorders]) 412 [tv setBorder: 4 : 4]; 413 else 414 [tv setBorder: 4 : 0]; 415 416 [tab_item setView:hb]; 417 DESTROY(hb); 418 419 [tab_view selectLastTabViewItem:nil]; 420 421 [[NSNotificationCenter defaultCenter] 422 addObserver: self 423 selector: @selector(_becameIdle:) 424 name: TerminalViewBecameIdleNotification 425 object: tv]; 426 [[NSNotificationCenter defaultCenter] 427 addObserver: self 428 selector: @selector(_becameNonIdle:) 429 name: TerminalViewBecameNonIdleNotification 430 object: tv]; 431 [[NSNotificationCenter defaultCenter] 432 addObserver: self 433 selector: @selector(_updateTitle:) 434 name: TerminalViewTitleDidChangeNotification 435 object: tv]; 436} 437 438-(void) closeTerminalTab:(TerminalView *)tv inWindow:(NSWindow *)window 439{ 440 NSTabViewItem *item; 441 NSInteger index; 442 NSInteger first_tab_index; 443 NSInteger last_tab_index; 444 445 if ([tab_view numberOfTabViewItems] == 1) { 446 [window performClose:nil]; 447 return; 448 } 449 450 item = [tab_view selectedTabViewItem]; 451 index = [tab_view indexOfTabViewItem:item]; 452 453 // Select new tab before removing old one 454 first_tab_index = 0; 455 last_tab_index = [tab_view numberOfTabViewItems] - 1; 456 // TODO A better tab selection heuristic (e.g. most recently used) 457 if (index == first_tab_index) { 458 [tab_view selectNextTabViewItem:nil]; 459 } else if (index == last_tab_index) { 460 [tab_view selectPreviousTabViewItem:nil]; 461 } else { 462 [tab_view selectNextTabViewItem:nil]; 463 } 464 465 [tab_view removeTabViewItem:item]; 466 [terminal_views removeObjectAtIndex:index]; 467 468 if ([tab_view numberOfTabViewItems] == 1) 469 { 470 NSView *view; 471 472 view = [[tab_view tabViewItemAtIndex:0] view]; 473 [view retain]; 474 [window setContentView: view]; 475 [view release]; 476 [window makeFirstResponder:[terminal_views objectAtIndex:0]]; 477 478 // Follow tab bar visible setting 479 [self setShowTabBar:[self showTabBar] inWindow:window]; 480 } 481 482 [tab_view display]; 483} 484 485-(void) showPreviousTab 486{ 487 // Clamp 488 NSInteger first_tab_index = 0; 489 NSTabViewItem *item = [tab_view selectedTabViewItem]; 490 NSInteger index = [tab_view indexOfTabViewItem:item]; 491 if (index == first_tab_index) { 492 return; 493 } 494 495 [tab_view selectPreviousTabViewItem:nil]; 496} 497 498-(void) showNextTab 499{ 500 // Clamp 501 NSInteger last_tab_index = [tab_view numberOfTabViewItems] - 1; 502 NSTabViewItem *item = [tab_view selectedTabViewItem]; 503 NSInteger index = [tab_view indexOfTabViewItem:item]; 504 if (index == last_tab_index) { 505 return; 506 } 507 508 [tab_view selectNextTabViewItem:nil]; 509} 510 511-(void) moveTabLeft 512{ 513 NSTabViewItem *old_item; 514 515 // Clamp 516 NSInteger first_tab_index = 0; 517 NSTabViewItem *item = [tab_view selectedTabViewItem]; 518 NSInteger index = [tab_view indexOfTabViewItem:item]; 519 if (index == first_tab_index) { 520 return; 521 } 522 523 NSInteger destination_index = index - 1; 524 525 TerminalView *old_tv = [terminal_views objectAtIndex:destination_index]; 526 TerminalView *new_tv = [terminal_views objectAtIndex:index]; 527 [terminal_views replaceObjectAtIndex:destination_index withObject:new_tv]; 528 [terminal_views replaceObjectAtIndex:index withObject:old_tv]; 529 530 old_item = [tab_view tabViewItemAtIndex:destination_index]; 531 [old_item retain]; 532 [tab_view removeTabViewItem:old_item]; 533 [tab_view insertTabViewItem:old_item atIndex:index]; 534 [old_item release]; 535 536 [self _updateTitleFromTerminalView:old_tv]; 537 [self _updateTitleFromTerminalView:new_tv]; 538 539 [tab_view selectTabViewItemAtIndex:destination_index]; 540 541 [tab_view display]; 542} 543 544-(void) moveTabRight 545{ 546 NSTabViewItem *old_item; 547 NSInteger destination_index; 548 TerminalView *old_tv; 549 TerminalView *new_tv; 550 551 // Clamp 552 NSInteger last_tab_index = [tab_view numberOfTabViewItems] - 1; 553 NSTabViewItem *item = [tab_view selectedTabViewItem]; 554 NSInteger index = [tab_view indexOfTabViewItem:item]; 555 if (index == last_tab_index) { 556 return; 557 } 558 559 destination_index = index + 1; 560 561 old_tv = [terminal_views objectAtIndex:destination_index]; 562 new_tv = [terminal_views objectAtIndex:index]; 563 [terminal_views replaceObjectAtIndex:destination_index withObject:new_tv]; 564 [terminal_views replaceObjectAtIndex:index withObject:old_tv]; 565 566 old_item = [tab_view tabViewItemAtIndex:destination_index]; 567 [old_item retain]; 568 [tab_view removeTabViewItem:old_item]; 569 [tab_view insertTabViewItem:old_item atIndex:index]; 570 [old_item release]; 571 572 [self _updateTitleFromTerminalView:old_tv]; 573 [self _updateTitleFromTerminalView:new_tv]; 574 575 [tab_view selectTabViewItemAtIndex:destination_index]; 576 577 [tab_view display]; 578} 579 580- (void) tabView: (NSTabView*)tabView didSelectTabViewItem: (NSTabViewItem*)tabViewItem 581{ 582 NSInteger tab_number = [tabView indexOfTabViewItem:tabViewItem]; 583 584 TerminalView *tv = [terminal_views objectAtIndex:tab_number]; 585 [self _updateTitleFromTerminalView:tv]; 586 587 [[self window] makeFirstResponder:[terminal_views objectAtIndex:tab_number]]; 588 [tv setNeedsDisplay:YES]; 589} 590 591@end 592 593