Skip to content

Commit

Permalink
Attach to existing tmux sessions when restoring a session that is a t…
Browse files Browse the repository at this point in the history
…mux gateway
  • Loading branch information
gnachman committed Jun 1, 2015
1 parent 3a278e9 commit 2da7801
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 50 deletions.
23 changes: 22 additions & 1 deletion sources/PTYSession.m
Expand Up @@ -98,6 +98,9 @@
static NSString *const SESSION_ARRANGEMENT_TMUX_HISTORY = @"Tmux History";
static NSString *const SESSION_ARRANGEMENT_TMUX_ALT_HISTORY = @"Tmux AltHistory";
static NSString *const SESSION_ARRANGEMENT_TMUX_STATE = @"Tmux State";
static NSString *const SESSION_ARRANGEMENT_IS_TMUX_GATEWAY = @"Is Tmux Gateway";
static NSString *const SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_NAME = @"Tmux Gateway Session Name";
static NSString *const SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_ID = @"Tmux Gateway Session ID";
static NSString *const SESSION_ARRANGEMENT_DEFAULT_NAME = @"Session Default Name"; // manually set name
static NSString *const SESSION_ARRANGEMENT_WINDOW_TITLE = @"Session Window Title"; // server-set window name
static NSString *const SESSION_ARRANGEMENT_NAME = @"Session Name"; // server-set "icon" (tab) name
Expand Down Expand Up @@ -667,17 +670,24 @@ + (PTYSession*)sessionFromArrangement:(NSDictionary *)arrangement
}
[aSession setTab:theTab];
NSNumber *n = [arrangement objectForKey:SESSION_ARRANGEMENT_TMUX_PANE];
BOOL shouldEnterTmuxMode = NO;
if (!n) {
DLog(@"No tmux pane ID during session restoration");
// |contents| will be non-nil when using system window restoration.
NSDictionary *contents = arrangement[SESSION_ARRANGEMENT_CONTENTS];

BOOL runCommand = YES;
if ([iTermAdvancedSettingsModel runJobsInServers]) {
if (arrangement[SESSION_ARRANGEMENT_SERVER_PID]) {
if ([arrangement[SESSION_ARRANGEMENT_IS_TMUX_GATEWAY] boolValue]) {
// Before attaching to the server we can put the parser into "tmux recovery mode".
[aSession.terminal.parser startTmuxRecoveryMode];
}
pid_t serverPid = [arrangement[SESSION_ARRANGEMENT_SERVER_PID] intValue];
if ([aSession tryToAttachToServerWithProcessId:serverPid]) {
runCommand = NO;
shouldEnterTmuxMode = ([arrangement[SESSION_ARRANGEMENT_IS_TMUX_GATEWAY] boolValue] &&
arrangement[SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_NAME] != nil &&
arrangement[SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_ID] != nil);
}
}
}
Expand Down Expand Up @@ -801,6 +811,11 @@ + (PTYSession*)sessionFromArrangement:(NSDictionary *)arrangement
inTab:theTab
forObjectType:objectType];
}
if (shouldEnterTmuxMode) {
[aSession startTmuxMode];
[aSession.tmuxController sessionChangedTo:arrangement[SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_NAME]
sessionId:[arrangement[SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_ID] intValue]];
}
return aSession;
}

Expand Down Expand Up @@ -3015,6 +3030,12 @@ - (NSDictionary *)arrangementWithContents:(BOOL)includeContents {
result[SESSION_ARRANGEMENT_CURSOR_POSITION] = [NSDictionary dictionaryWithGridCoord:cursor];
}
}
if (self.tmuxMode == TMUX_GATEWAY && self.tmuxController.sessionName) {
result[SESSION_ARRANGEMENT_IS_TMUX_GATEWAY] = @YES;
result[SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_ID] = @(self.tmuxController.sessionId);
result[SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_NAME] = self.tmuxController.sessionName;
}

result[SESSION_ARRANGEMENT_VARIABLES] = _variables;
NSString *pwd = [self currentLocalWorkingDirectory];
result[SESSION_ARRANGEMENT_WORKING_DIRECTORY] = pwd ? pwd : @"";
Expand Down
39 changes: 8 additions & 31 deletions sources/TmuxController.h
Expand Up @@ -31,38 +31,15 @@ extern NSString *kTmuxControllerAttachedSessionDidChange;
// Posted when a session changes name
extern NSString *kTmuxControllerSessionWasRenamed;

@interface TmuxController : NSObject {
TmuxGateway *gateway_;
NSMutableDictionary *windowPanes_; // paneId -> PTYSession *
NSMutableDictionary *windows_; // window -> [PTYTab *, refcount]
NSArray *sessions_;
int numOutstandingWindowResizes_;
NSMutableDictionary *windowPositions_;
NSSize lastSize_; // last size for windowDidChange:
NSString *lastOrigins_;
BOOL detached_;
NSString *sessionName_;
int sessionId_;
NSMutableSet *pendingWindowOpens_;
NSString *lastSaveAffinityCommand_;
// tmux windows that want to open as tabs in the same physical window
// belong to the same equivalence class.
EquivalenceClassSet *affinities_;
BOOL windowOriginsDirty_;
BOOL haveOutstandingSaveWindowOrigins_;
NSMutableDictionary *origins_; // window id -> NSValue(Point) window origin
NSMutableSet *hiddenWindows_;
NSTimer *listSessionsTimer_; // Used to do a cancelable delayed perform of listSessions.
NSTimer *listWindowsTimer_; // Used to do a cancelable delayed perform of listWindows.
BOOL ambiguousIsDoubleWidth_;
}
@interface TmuxController : NSObject

@property (nonatomic, readonly) TmuxGateway *gateway;
@property (nonatomic, retain) NSMutableDictionary *windowPositions;
@property (nonatomic, copy) NSString *sessionName;
@property (nonatomic, retain) NSArray *sessions;
@property (nonatomic, assign) BOOL ambiguousIsDoubleWidth;
@property (nonatomic, readonly) NSString *clientName;
@property(nonatomic, readonly) TmuxGateway *gateway;
@property(nonatomic, retain) NSMutableDictionary *windowPositions;
@property(nonatomic, copy) NSString *sessionName;
@property(nonatomic, retain) NSArray *sessions;
@property(nonatomic, assign) BOOL ambiguousIsDoubleWidth;
@property(nonatomic, readonly) NSString *clientName;
@property(nonatomic, readonly) int sessionId;

- (id)initWithGateway:(TmuxGateway *)gateway clientName:(NSString *)clientName;
- (void)openWindowsInitial;
Expand Down
27 changes: 26 additions & 1 deletion sources/TmuxController.m
Expand Up @@ -48,13 +48,38 @@ @interface TmuxController ()

@end

@implementation TmuxController
@implementation TmuxController {
TmuxGateway *gateway_;
NSMutableDictionary *windowPanes_; // paneId -> PTYSession *
NSMutableDictionary *windows_; // window -> [PTYTab *, refcount]
NSArray *sessions_;
int numOutstandingWindowResizes_;
NSMutableDictionary *windowPositions_;
NSSize lastSize_; // last size for windowDidChange:
NSString *lastOrigins_;
BOOL detached_;
NSString *sessionName_;
int sessionId_;
NSMutableSet *pendingWindowOpens_;
NSString *lastSaveAffinityCommand_;
// tmux windows that want to open as tabs in the same physical window
// belong to the same equivalence class.
EquivalenceClassSet *affinities_;
BOOL windowOriginsDirty_;
BOOL haveOutstandingSaveWindowOrigins_;
NSMutableDictionary *origins_; // window id -> NSValue(Point) window origin
NSMutableSet *hiddenWindows_;
NSTimer *listSessionsTimer_; // Used to do a cancelable delayed perform of listSessions.
NSTimer *listWindowsTimer_; // Used to do a cancelable delayed perform of listWindows.
BOOL ambiguousIsDoubleWidth_;
}

@synthesize gateway = gateway_;
@synthesize windowPositions = windowPositions_;
@synthesize sessionName = sessionName_;
@synthesize sessions = sessions_;
@synthesize ambiguousIsDoubleWidth = ambiguousIsDoubleWidth_;
@synthesize sessionId = sessionId_;

- (id)initWithGateway:(TmuxGateway *)gateway clientName:(NSString *)clientName
{
Expand Down
2 changes: 2 additions & 0 deletions sources/VT100ControlParser.h
Expand Up @@ -31,5 +31,7 @@ NS_INLINE BOOL iscontrol(int c) {
savedState:(NSMutableDictionary *)savedState
dcsHooked:(BOOL *)dcsHooked;

- (void)startTmuxRecoveryMode;

@end

4 changes: 4 additions & 0 deletions sources/VT100ControlParser.m
Expand Up @@ -128,4 +128,8 @@ - (void)parseControlWithData:(unsigned char *)datap
}
}

- (void)startTmuxRecoveryMode {
[_dcsParser startTmuxRecoveryMode];
}

@end
6 changes: 4 additions & 2 deletions sources/VT100DCSParser.h
Expand Up @@ -67,6 +67,9 @@ typedef enum {
// For debug logging; nil if no hook.
@property(nonatomic, readonly) NSString *hookDescription;

+ (NSDictionary *)termcapTerminfoNameDictionary; // string name -> DcsTermcapTerminfoRequestName
+ (NSDictionary *)termcapTerminfoInverseNameDictionary; // DcsTermcapTerminfoRequestName -> string name

- (void)decodeFromContext:(iTermParserContext *)context
token:(VT100Token *)result
encoding:(NSStringEncoding)encoding
Expand All @@ -75,8 +78,7 @@ typedef enum {
// Reset to ground state, unhooking if needed.
- (void)reset;

+ (NSDictionary *)termcapTerminfoNameDictionary; // string name -> DcsTermcapTerminfoRequestName
+ (NSDictionary *)termcapTerminfoInverseNameDictionary; // DcsTermcapTerminfoRequestName -> string name
- (void)startTmuxRecoveryMode;

@end

Expand Down
42 changes: 28 additions & 14 deletions sources/VT100DCSParser.m
Expand Up @@ -67,6 +67,22 @@ @implementation VT100DCSParser {
id<VT100DCSParserHook> _hook;
}

+ (NSDictionary *)termcapTerminfoNameDictionary {
return @{ @"TN": @(kDcsTermcapTerminfoRequestTerminalName),
@"name": @(kDcsTermcapTerminfoRequestTerminfoName),
@"iTerm2Profile": @(kDcsTermcapTerminfoRequestiTerm2ProfileName) };
}

+ (NSDictionary *)termcapTerminfoInverseNameDictionary {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSDictionary *dict = [self termcapTerminfoNameDictionary];
for (NSString *key in dict) {
id value = dict[key];
result[value] = key;
}
return result;
}

- (id)init {
self = [super init];
if (self) {
Expand Down Expand Up @@ -380,7 +396,9 @@ - (void)hook {
if ([self compactSequence] == MAKE_COMPACT_SEQUENCE(0, 0, 'p') &&
[[self parameters] isEqual:@[ @"1000" ]]) {
VT100Token *token = _stateMachine.userInfo[kVT100DCSUserInfoToken];
token->type = DCS_TMUX_HOOK;
if (token) {
token->type = DCS_TMUX_HOOK;
}

[_hook release];
_hook = [[VT100TmuxParser alloc] init];
Expand Down Expand Up @@ -485,20 +503,16 @@ - (void)parseTermcapTerminfoToken:(VT100Token *)token {
}
}

+ (NSDictionary *)termcapTerminfoNameDictionary {
return @{ @"TN": @(kDcsTermcapTerminfoRequestTerminalName),
@"name": @(kDcsTermcapTerminfoRequestTerminfoName),
@"iTerm2Profile": @(kDcsTermcapTerminfoRequestiTerm2ProfileName) };
}

+ (NSDictionary *)termcapTerminfoInverseNameDictionary {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSDictionary *dict = [self termcapTerminfoNameDictionary];
for (NSString *key in dict) {
id value = dict[key];
result[value] = key;
- (void)startTmuxRecoveryMode {
// Put the state machine in the passthrough mode.
char *fakeControlSequence = "\eP1000p";
for (int i = 0; fakeControlSequence[i]; i++) {
[_stateMachine handleCharacter:fakeControlSequence[i]];
}
return result;

// Replace the hook with one in recovery mode.
[_hook release];
_hook = [[VT100TmuxParser alloc] initInRecoveryMode];
}

@end
Expand Down
1 change: 1 addition & 0 deletions sources/VT100Parser.h
Expand Up @@ -21,6 +21,7 @@
- (void)putStreamData:(const char *)buffer length:(int)length;
- (void)clearStream;
- (void)forceUnhookDCS;
- (void)startTmuxRecoveryMode;

// CVector was created for this method. Because so many VT100Token*s are created and destroyed,
// too much time is spent adjusting their retain counts. Since an iTermObjectPool is used to avoid
Expand Down
7 changes: 7 additions & 0 deletions sources/VT100Parser.m
Expand Up @@ -237,4 +237,11 @@ - (void)addParsedTokensToVector:(CVector *)vector {
}
}

- (void)startTmuxRecoveryMode {
@synchronized(self) {
[_controlParser startTmuxRecoveryMode];
_dcsHooked = YES;
}
}

@end
1 change: 1 addition & 0 deletions sources/VT100TmuxParser.h
Expand Up @@ -12,4 +12,5 @@
#import "VT100DCSParser.h"

@interface VT100TmuxParser : NSObject <VT100DCSParserHook>
- (instancetype)initInRecoveryMode;
@end
29 changes: 28 additions & 1 deletion sources/VT100TmuxParser.m
Expand Up @@ -16,9 +16,18 @@ @interface VT100TmuxParser ()

@implementation VT100TmuxParser {
BOOL _inResponseBlock;
BOOL _recoveryMode;
NSMutableData *_line;
}

- (instancetype)initInRecoveryMode {
self = [self init];
if (self) {
_recoveryMode = YES;
}
return self;
}

- (void)dealloc {
[_currentCommandId release];
[_currentCommandNumber release];
Expand Down Expand Up @@ -57,7 +66,25 @@ - (BOOL)handleInput:(iTermParserContext *)context token:(VT100Token *)result {

// Tokenize the line, returning if it is a terminator like %exit.
if ([self processLineIntoToken:result]) {
return YES;
if (_recoveryMode) {
// In recovery mode, we always ignore the first line unless it is a %begin.
// That's because we expect we came into an existing connection, but not one that
// is responding to a command. We could start reading in the middle of a notification,
// such as half the line of an %output. Notifications always fit on a single line
// so ignoring the first line is safe. If we came in to a silent connection then
// the first thing we'll get back from the server that we do expect to see is a
// response, so we don't want to ignore that.
if ([result.string hasPrefix:@"%begin"]) {
return YES;
} else {
result->type = VT100_WAIT;
result.string = nil;
_recoveryMode = NO;
return NO;
}
} else {
return YES;
}
}
}
return NO;
Expand Down

0 comments on commit 2da7801

Please sign in to comment.