From 538d570ea54614d3a2b5724f820953d717fbeb0c Mon Sep 17 00:00:00 2001 From: George Nachman Date: Wed, 25 Sep 2019 23:13:00 -0700 Subject: [PATCH] Do not send server-controlled values in tmux integration mode. CVE-2019-9535 - Use session number everywhere rather than session name - Do not poll tmux for the set-titles-string, status-left, and status-right and then request the values of the returned format strings. Use ${T:} eval instead. These features are now only available for tmux 2.9 and later. - Hex-encode options saved in the tmux server to make them unexploitable (e.g., hotkeys, window affinities, window origins, etc.). The old values are accepted as inputs but will never be produced as output. --- iTerm2.xcodeproj/project.pbxproj | 8 + sources/PTYSession.m | 28 ++- sources/PTYTab.m | 13 +- sources/PseudoTerminal.m | 9 +- sources/TmuxController.h | 47 ++-- sources/TmuxController.m | 305 +++++++++++++++----------- sources/TmuxDashboardController.m | 163 +++++++------- sources/TmuxSessionsTable.h | 30 +-- sources/TmuxSessionsTable.m | 106 ++++----- sources/TmuxWindowsTable.h | 2 +- sources/TmuxWindowsTable.m | 21 +- sources/iTermInitialDirectory+Tmux.h | 2 +- sources/iTermInitialDirectory+Tmux.m | 20 +- sources/iTermTmuxOptionMonitor.h | 4 + sources/iTermTmuxOptionMonitor.m | 16 +- sources/iTermTmuxSessionObject.h | 17 ++ sources/iTermTmuxSessionObject.m | 12 + sources/iTermTmuxStatusBarMonitor.h | 1 + sources/iTermTmuxStatusBarMonitor.m | 28 +-- sources/iTermWorkingDirectoryPoller.m | 1 + 20 files changed, 471 insertions(+), 362 deletions(-) create mode 100644 sources/iTermTmuxSessionObject.h create mode 100644 sources/iTermTmuxSessionObject.m diff --git iTerm2.xcodeproj/project.pbxproj iTerm2.xcodeproj/project.pbxproj index 7e2f86a..32e1df2 100644 --- iTerm2.xcodeproj/project.pbxproj +++ iTerm2.xcodeproj/project.pbxproj @@ -1376,6 +1376,8 @@ 53E184F21FE32F2800DB78F3 /* iTermMetalBufferPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E184F01FE32F2800DB78F3 /* iTermMetalBufferPool.m */; }; 53E8F36F2244A58800F3770F /* iTermActionsModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E8F36D2244A58800F3770F /* iTermActionsModel.h */; }; 53E8F3702244A58800F3770F /* iTermActionsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E8F36E2244A58800F3770F /* iTermActionsModel.m */; }; + 53E98E7B233C6B760094D8A9 /* iTermTmuxSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */; }; + 53E98E7C233C6B760094D8A9 /* iTermTmuxSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */; }; 53E9DFE0220D518E0070C9C0 /* AlertTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DCC15142D7FC10016228A /* AlertTrigger.m */; }; 53E9DFE1220D51D50070C9C0 /* CoprocessTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DDD91142E5AC600275650 /* CoprocessTrigger.m */; }; 53E9DFE2220D52730070C9C0 /* PasswordTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DABA03219253FEA00A228D8 /* PasswordTrigger.m */; }; @@ -4429,6 +4431,8 @@ 53E184F01FE32F2800DB78F3 /* iTermMetalBufferPool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermMetalBufferPool.m; sourceTree = ""; }; 53E8F36D2244A58800F3770F /* iTermActionsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermActionsModel.h; sourceTree = ""; }; 53E8F36E2244A58800F3770F /* iTermActionsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermActionsModel.m; sourceTree = ""; }; + 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermTmuxSessionObject.h; sourceTree = ""; }; + 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermTmuxSessionObject.m; sourceTree = ""; }; 53EBF29B1DCBFF7C00766613 /* iTermAPIServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermAPIServer.h; sourceTree = ""; }; 53EBF29C1DCBFF7C00766613 /* iTermAPIServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermAPIServer.m; sourceTree = ""; }; 53EBF29F1DCCF4AC00766613 /* iTermIPV4Address.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermIPV4Address.h; sourceTree = ""; }; @@ -7010,6 +7014,8 @@ A6F718B4226438FC0053488E /* iTermInitialDirectory+Tmux.m */, 53D68F7E2283EE7C0018710D /* iTermTmuxLayoutBuilder.h */, 53D68F7F2283EE7C0018710D /* iTermTmuxLayoutBuilder.m */, + 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */, + 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */, ); name = tmux; sourceTree = ""; @@ -9987,6 +9993,7 @@ A6153D4C21F30A9C002976FC /* iTermJobTreeViewController.h in Headers */, 5370678F21C9D2780088D0F3 /* SIGSHA2VerificationAlgorithm.h in Headers */, A66719161DCE36C3000CE608 /* NSURL+iTerm.h in Headers */, + 53E98E7B233C6B760094D8A9 /* iTermTmuxSessionObject.h in Headers */, A66719171DCE36C3000CE608 /* NSDate+iTerm.h in Headers */, A6BCAAD821F6F53E0000CD29 /* iTermTextPopoverViewController.h in Headers */, A66719181DCE36C3000CE608 /* iTermBaseHotKey.h in Headers */, @@ -12429,6 +12436,7 @@ A6AC5D241E9036D70097C0A7 /* iTermURLMark.m in Sources */, 535EA51420D8C1EB00FC81E0 /* iTermProfilesWindowController.m in Sources */, A6A4866F20B67A1600493302 /* iTermSemanticHistoryPrefsController.m in Sources */, + 53E98E7C233C6B760094D8A9 /* iTermTmuxSessionObject.m in Sources */, A66719621DCE3772000CE608 /* iTermSocketAddress.m in Sources */, A6A4867E20B67FD100493302 /* SmartSelectionController.m in Sources */, 53AFFC8E1DD2A04100E6CEC6 /* iTermLSOF.m in Sources */, diff --git sources/PTYSession.m sources/PTYSession.m index 9b64e03..0332d8c 100644 --- sources/PTYSession.m +++ sources/PTYSession.m @@ -4726,7 +4726,7 @@ ITERM_WEAKLY_REFERENCEABLE } else { // Legacy code path for pre tmux 2.6 [_tmuxController renameWindowWithId:_delegate.tmuxWindow - inSession:nil + inSessionNumber:nil toName:profile[KEY_NAME]]; } _tmuxTitleOutOfSync = NO; @@ -5910,14 +5910,18 @@ scrollToFirstResult:(BOOL)scrollToFirstResult { - (void)installTmuxStatusBarMonitor { assert(!_tmuxStatusBarMonitor); - _tmuxStatusBarMonitor = [[iTermTmuxStatusBarMonitor alloc] initWithGateway:_tmuxController.gateway - scope:self.variablesScope]; - _tmuxStatusBarMonitor.active = [iTermProfilePreferences boolForKey:KEY_SHOW_STATUS_BAR inProfile:self.profile]; - if ([iTermPreferences boolForKey:kPreferenceKeyUseTmuxStatusBar] || - [iTermStatusBarLayout shouldOverrideLayout:self.profile[KEY_STATUS_BAR_LAYOUT]]) { - [self setSessionSpecificProfileValues:@{ KEY_STATUS_BAR_LAYOUT: [[iTermStatusBarLayout tmuxLayoutWithController:_tmuxController - scope:nil - window:self.delegate.tmuxWindow] dictionaryValue] }]; + + if (_tmuxController.gateway.minimumServerVersion.doubleValue >= 2.9) { + // Just use the built-in status bar for older versions of tmux because they don't support ${T:xxx} or ${E:xxx} + _tmuxStatusBarMonitor = [[iTermTmuxStatusBarMonitor alloc] initWithGateway:_tmuxController.gateway + scope:self.variablesScope]; + _tmuxStatusBarMonitor.active = [iTermProfilePreferences boolForKey:KEY_SHOW_STATUS_BAR inProfile:self.profile]; + if ([iTermPreferences boolForKey:kPreferenceKeyUseTmuxStatusBar] || + [iTermStatusBarLayout shouldOverrideLayout:self.profile[KEY_STATUS_BAR_LAYOUT]]) { + [self setSessionSpecificProfileValues:@{ KEY_STATUS_BAR_LAYOUT: [[iTermStatusBarLayout tmuxLayoutWithController:_tmuxController + scope:nil + window:self.delegate.tmuxWindow] dictionaryValue] }]; + } } } @@ -5929,7 +5933,8 @@ scrollToFirstResult:(BOOL)scrollToFirstResult { } __weak __typeof(self) weakSelf = self; _tmuxTitleMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:_tmuxController.gateway - scope:self.variablesScope + scope:self.variablesScope + fallbackVariableName:nil format:@"#{pane_title}" target:[NSString stringWithFormat:@"%%%@", @(self.tmuxPane)] variableName:iTermVariableKeySessionTmuxPaneTitle @@ -6308,8 +6313,7 @@ scrollToFirstResult:(BOOL)scrollToFirstResult { [_tmuxController ping]; [_tmuxController validateOptions]; [_tmuxController checkForUTF8]; - [_tmuxController guessVersion]; - [_tmuxController loadTitleFormat]; + [_tmuxController guessVersion]; // NOTE: This kicks off more stuff that depends on knowing the version number. } - (void)tmuxInitialCommandDidFailWithError:(NSString *)error { diff --git sources/PTYTab.m sources/PTYTab.m index a8f8b48..6c4766e 100644 --- sources/PTYTab.m +++ sources/PTYTab.m @@ -3690,11 +3690,12 @@ static void SetAgainstGrainDim(BOOL isVertical, NSSize *dest, CGFloat value) { return; } _tmuxTitleMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:tmuxController_.gateway - scope:self.variablesScope - format:tmuxController_.setTitlesString - target:[NSString stringWithFormat:@"@%@", @(self.tmuxWindow)] - variableName:iTermVariableKeyTabTmuxWindowTitle - block:nil]; + scope:self.variablesScope + fallbackVariableName:iTermVariableKeySessionWindowName + format:@"#{T:set-titles-string}" + target:[NSString stringWithFormat:@"@%@", @(self.tmuxWindow)] + variableName:iTermVariableKeyTabTmuxWindowTitle + block:nil]; [_tmuxTitleMonitor updateOnce]; if (self.titleOverride.length == 0) { // Show the tmux window title if both the tmux option set-titles is on and the user hasn't @@ -4312,7 +4313,7 @@ static void SetAgainstGrainDim(BOOL isVertical, NSSize *dest, CGFloat value) { if (self.tmuxTab) { if (titleOverride) { [self.tmuxController renameWindowWithId:self.tmuxWindow - inSession:nil + inSessionNumber:nil toName:titleOverride]; } return; diff --git sources/PseudoTerminal.m sources/PseudoTerminal.m index b90e733..24f041a 100644 --- sources/PseudoTerminal.m +++ sources/PseudoTerminal.m @@ -3018,8 +3018,8 @@ ITERM_WEAKLY_REFERENCEABLE if ([arrangement objectForKey:TERMINAL_GUID] && [[arrangement objectForKey:TERMINAL_GUID] isKindOfClass:[NSString class]]) { NSString *savedGUID = [arrangement objectForKey:TERMINAL_GUID]; - if ([[iTermController sharedInstance] terminalWithGuid:savedGUID]) { - // Refuse to create a window with an already-used guid. + if ([[iTermController sharedInstance] terminalWithGuid:savedGUID] || ![self stringIsValidTerminalGuid:savedGUID]) { + // Refuse to create a window with an already-used or invalid guid. self.terminalGuid = [NSString stringWithFormat:@"pty-%@", [NSString uuid]]; } else { self.terminalGuid = savedGUID; @@ -3039,6 +3039,11 @@ ITERM_WEAKLY_REFERENCEABLE return YES; } +- (BOOL)stringIsValidTerminalGuid:(NSString *)string { + NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"]; + return [string rangeOfCharacterFromSet:characterSet.invertedSet].location == NSNotFound; +} + - (BOOL)restoreTabsFromArrangement:(NSDictionary *)arrangement sessions:(NSArray *)sessions { for (NSDictionary *tabArrangement in arrangement[TERMINAL_ARRANGEMENT_TABS]) { NSDictionary *sessionMap = nil; diff --git sources/TmuxController.h sources/TmuxController.h index f27bacc..f1e5ad3 100644 --- sources/TmuxController.h +++ sources/TmuxController.h @@ -8,6 +8,7 @@ #import #import "ProfileModel.h" #import "iTermInitialDirectory.h" +#import "iTermTmuxSessionObject.h" #import "TmuxGateway.h" #import "WindowControllerInterface.h" @@ -33,7 +34,7 @@ extern NSString *const kTmuxControllerWindowDidClose; extern NSString *const kTmuxControllerAttachedSessionDidChange; // Posted when a session changes name extern NSString *const kTmuxControllerSessionWasRenamed; -// Posted when set-titles-string option changes. Object is tmux controller. +// Posted when set-titles option changes. Object is tmux controller. extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption; @interface TmuxController : NSObject @@ -41,7 +42,7 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption; @property(nonatomic, readonly) TmuxGateway *gateway; @property(nonatomic, retain) NSMutableDictionary *windowPositions; @property(nonatomic, copy) NSString *sessionName; -@property(nonatomic, retain) NSArray *sessions; +@property(nonatomic, copy) NSArray *sessionObjects; @property(nonatomic, assign) BOOL ambiguousIsDoubleWidth; @property(nonatomic, assign) NSInteger unicodeVersion; @property(nonatomic, readonly) NSString *clientName; @@ -53,7 +54,6 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption; @property(nonatomic, readonly) NSDictionary *sharedFontOverrides; @property(nonatomic, readonly) NSString *sessionGuid; @property(nonatomic, readonly) BOOL variableWindowSize; -@property(nonatomic, readonly) NSString *setTitlesString; @property(nonatomic, readonly) BOOL shouldSetTitles; @property(nonatomic, readonly) BOOL serverIsLocal; @@ -120,9 +120,9 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption; scope:(iTermVariableScope *)scope initialDirectory:(iTermInitialDirectory *)initialDirectory; -- (void)newWindowInSession:(NSString *)targetSession - scope:(iTermVariableScope *)scope - initialDirectory:(iTermInitialDirectory *)initialDirectory; +- (void)newWindowInSessionNumber:(NSNumber *)sessionNumber + scope:(iTermVariableScope *)scope + initialDirectory:(iTermInitialDirectory *)initialDirectory; - (void)selectPane:(int)windowPane; @@ -150,9 +150,11 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption; - (void)killWindowPane:(int)windowPane; - (void)killWindow:(int)window; -- (void)unlinkWindowWithId:(int)windowId inSession:(NSString *)sessionName; +- (void)unlinkWindowWithId:(int)windowId; - (void)requestDetach; -- (void)renameWindowWithId:(int)windowId inSession:(NSString *)sessionName toName:(NSString *)newName; +- (void)renameWindowWithId:(int)windowId + inSessionNumber:(NSNumber *)sessionNumber + toName:(NSString *)newName; - (BOOL)canRenamePane; - (void)renamePane:(int)windowPane toTitle:(NSString *)newTitle; - (void)setHotkeyForWindowPane:(int)windowPane to:(NSDictionary *)hotkey; @@ -162,21 +164,25 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption; - (NSString *)tabColorStringForWindowPane:(int)windowPane; - (void)linkWindowId:(int)windowId - inSession:(NSString *)sessionName - toSession:(NSString *)targetSession; + inSessionNumber:(int)sessionNumber + toSessionNumber:(int)targetSession; + - (void)moveWindowId:(int)windowId - inSession:(NSString *)sessionName - toSession:(NSString *)targetSession; + inSessionNumber:(int)sessionNumber + toSessionNumber:(int)targetSessionNumber; + +- (void)renameSessionNumber:(int)sessionNumber + to:(NSString *)newName; -- (void)renameSession:(NSString *)oldName to:(NSString *)newName; -- (void)killSession:(NSString *)sessionName; -- (void)attachToSession:(NSString *)sessionName; +- (void)killSessionNumber:(int)sessionNumber; +- (void)attachToSessionWithNumber:(int)sessionNumber; - (void)addSessionWithName:(NSString *)sessionName; -// NOTE: If the session name is bogus (or any other error occurs) the selector will not be called. -- (void)listWindowsInSession:(NSString *)sessionName - target:(id)target - selector:(SEL)selector - object:(id)object; +// NOTE: If anything goes wrong the selector will not be called. +- (void)listWindowsInSessionNumber:(int)sessionNumber + target:(id)target + selector:(SEL)selector + object:(id)object; + - (void)listSessions; - (void)saveAffinities; - (void)saveWindowOrigins; @@ -200,7 +206,6 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption; - (void)setLayoutInWindow:(int)window toLayout:(NSString *)layout; - (NSArray *)clientSessions; -- (void)setSize:(NSSize)size windows:(NSArray *)windows; - (void)setSize:(NSSize)size window:(int)window; @end diff --git sources/TmuxController.m sources/TmuxController.m index 08cbd91..77b0424 100644 --- sources/TmuxController.m +++ sources/TmuxController.m @@ -20,6 +20,7 @@ #import "iTermShortcut.h" #import "iTermTuple.h" #import "NSArray+iTerm.h" +#import "NSData+iTerm.h" #import "NSFont+iTerm.h" #import "NSStringITerm.h" #import "PreferencePanel.h" @@ -42,6 +43,12 @@ NSString *const kTmuxControllerWindowDidClose = @"kTmuxControllerWindowDidClose" NSString *const kTmuxControllerSessionWasRenamed = @"kTmuxControllerSessionWasRenamed"; NSString *const kTmuxControllerDidFetchSetTitlesStringOption = @"kTmuxControllerDidFetchSetTitlesStringOption"; +static NSString *const iTermTmuxControllerEncodingPrefixHotkeys = @"h_"; +static NSString *const iTermTmuxControllerEncodingPrefixTabColors = @"t_"; +static NSString *const iTermTmuxControllerEncodingPrefixAffinities = @"a_"; +static NSString *const iTermTmuxControllerEncodingPrefixOrigins = @"o_"; +static NSString *const iTermTmuxControllerEncodingPrefixHidden = @"i_"; + // Unsupported global options: static NSString *const kAggressiveResize = @"aggressive-resize"; @@ -81,7 +88,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t" TmuxGateway *gateway_; NSMutableDictionary *windowPanes_; // paneId -> PTYSession * NSMutableDictionary *_windowStates; // Key is window number - NSArray *sessions_; + NSArray *sessionObjects_; int numOutstandingWindowResizes_; NSMutableDictionary *windowPositions_; NSSize lastSize_; // last size for windowDidChange: @@ -97,7 +104,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t" BOOL windowOriginsDirty_; BOOL haveOutstandingSaveWindowOrigins_; NSMutableDictionary *origins_; // window id -> NSValue(Point) window origin - NSMutableSet *hiddenWindows_; + 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_; @@ -119,7 +126,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t" @synthesize gateway = gateway_; @synthesize windowPositions = windowPositions_; @synthesize sessionName = sessionName_; -@synthesize sessions = sessions_; +@synthesize sessionObjects = sessionObjects_; @synthesize ambiguousIsDoubleWidth = ambiguousIsDoubleWidth_; @synthesize sessionId = sessionId_; @@ -180,8 +187,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile [_sharedFontOverrides release]; [_pendingWindows release]; [sessionName_ release]; - [sessions_ release]; - [_setTitlesString release]; + [sessionObjects_ release]; [super dealloc]; } @@ -392,16 +398,22 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile } [windowsToOpen addObject:record]; } + BOOL tooMany = NO; if (windowsToOpen.count > [iTermPreferences intForKey:kPreferenceKeyTmuxDashboardLimit]) { DLog(@"There are too many windows to open so just show the dashboard"); haveHidden = YES; + tooMany = YES; [windowsToOpen removeAllObjects]; } if (haveHidden) { DLog(@"Hidden windows existing, showing dashboard"); [[TmuxDashboardController sharedInstance] showWindow:nil]; [[[TmuxDashboardController sharedInstance] window] makeKeyAndOrderFront:nil]; - [[iTermNotificationController sharedInstance] notify:@"Too many tmux windows!" withDescription:@"Use the tmux dashboard to select which to open."]; + if (tooMany) { + [[iTermNotificationController sharedInstance] notify:@"Too many tmux windows!" withDescription:@"Use the tmux dashboard to select which to open."]; + } else { + [[iTermNotificationController sharedInstance] notify:@"Some tmux windows were hidden." withDescription:@"Use the tmux dashboard to select which to open."]; + } } for (NSArray *record in windowsToOpen) { DLog(@"Open window %@", record); @@ -459,7 +471,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile NSString *setSizeCommand = [NSString stringWithFormat:@"refresh-client -C %d,%d", size.width, [self adjustHeightForStatusBar:size.height]]; NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@", kListWindowsFormat]; - NSString *listSessionsCommand = @"list-sessions -F \"#{session_name}\""; + NSString *listSessionsCommand = @"list-sessions -F \"#{session_id} #{session_name}\""; NSString *getAffinitiesCommand = [NSString stringWithFormat:@"show -v -q -t $%d @affinities", sessionId_]; NSString *getOriginsCommand = [NSString stringWithFormat:@"show -v -q -t $%d @origins", sessionId_]; NSString *getHotkeysCommand = [NSString stringWithFormat:@"show -v -q -t $%d @hotkeys", sessionId_]; @@ -715,10 +727,6 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile } } -- (void)setSize:(NSSize)size windows:(NSArray *)windows { - [gateway_ sendCommandList:[self commandListToSetSize:size ofWindows:windows]]; -} - - (void)setWindowSizes:(NSArray *> *)windowSizes { [gateway_ sendCommandList:[self commandListToSetWindowSizes:windowSizes]]; } @@ -727,14 +735,14 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile return [windowSizes mapWithBlock:^NSDictionary *(iTermTuple *tuple) { NSString *window = tuple.firstObject; NSSize size = tuple.secondObject.sizeValue; - if ([window hasPrefix:@"pty"]) { + if ([window hasPrefix:@"pty"] || [window hasSuffix:@"_ph"]) { return nil; } // 10000 comes from WINDOW_MAXIMUM in tmux.h if (size.width < 1 || size.height < 1 || size.width >= 10000 || size.height >= 10000) { return nil; } - NSString *command = [NSString stringWithFormat:@"resize-window -x %@ -y %@ -t @%@", @((int)size.width), @((int)size.height), window]; + NSString *command = [NSString stringWithFormat:@"resize-window -x %@ -y %@ -t @%d", @((int)size.width), @((int)size.height), window.intValue]; NSDictionary *dict = [gateway_ dictionaryForCommand:command responseTarget:self responseSelector:@selector(handleResizeWindowResponse:) @@ -857,11 +865,6 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile [gateway_ sendCommandList:@[ [gateway_ dictionaryForCommand:@"show-options -v -g set-titles" responseTarget:self responseSelector:@selector(handleShowSetTitles:) - responseObject:nil - flags:0], - [gateway_ dictionaryForCommand:@"show-options -v -g set-titles-string" - responseTarget:self - responseSelector:@selector(handleShowSetTitlesString:) responseObject:nil flags:0] ]]; } @@ -872,10 +875,6 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile object:self]; } -- (void)handleShowSetTitlesString:(NSString *)setTitlesString { - _setTitlesString = [setTitlesString copy]; -} - - (void)guessVersion { // Run commands that will fail in successively older versions. // show-window-options pane-border-format will succeed in 2.3 and later (presumably. 2.3 isn't out yet) @@ -1013,17 +1012,25 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile } // This is the oldest version supported. By the time you get here you know the version. + [self didGuessVersion]; +} + +// Actions to perform after the version number is known. +- (void)didGuessVersion { [self loadServerPID]; + [self loadTitleFormat]; } -- (BOOL)recyclingSupported { - NSDecimalNumber *version1_9 = [NSDecimalNumber decimalNumberWithString:@"1.9"]; - if (gateway_.minimumServerVersion != nil) { - return ([gateway_.minimumServerVersion compare:version1_9] != NSOrderedAscending); - } else { - // Assume 1.8 +- (BOOL)versionAtLeastDecimalNumberWithString:(NSString *)string { + NSDecimalNumber *version = [NSDecimalNumber decimalNumberWithString:string]; + if (gateway_.minimumServerVersion == nil) { return NO; } + return ([gateway_.minimumServerVersion compare:version] != NSOrderedAscending); +} + +- (BOOL)recyclingSupported { + return [self versionAtLeastDecimalNumberWithString:@"1.9"]; } // Show an error and terminate the connection because tmux has an unsupported option turned on. @@ -1143,10 +1150,10 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile responseSelector:nil]; } -- (void)newWindowInSession:(NSString *)targetSession - scope:(iTermVariableScope *)scope - initialDirectory:(iTermInitialDirectory *)initialDirectory { - [initialDirectory tmuxNewWindowCommandInSession:targetSession +- (void)newWindowInSessionNumber:(NSNumber *)sessionNumber + scope:(iTermVariableScope *)scope + initialDirectory:(iTermInitialDirectory *)initialDirectory { + [initialDirectory tmuxNewWindowCommandInSessionNumber:sessionNumber recyclingSupported:self.recyclingSupported scope:scope completion: @@ -1164,7 +1171,11 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile responseObject:nil flags:0]]; } - [commands addObject:[gateway_ dictionaryForCommand:command responseTarget:nil responseSelector:nil responseObject:nil flags:0]]; + [commands addObject:[gateway_ dictionaryForCommand:command + responseTarget:nil + responseSelector:nil + responseObject:nil + flags:0]]; [gateway_ sendCommandList:commands]; }]; } @@ -1218,8 +1229,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile responseSelector:nil]; } -- (void)unlinkWindowWithId:(int)windowId inSession:(NSString *)sessionName -{ +- (void)unlinkWindowWithId:(int)windowId { [gateway_ sendCommand:[NSString stringWithFormat:@"unlink-window -k -t @%d", windowId] responseTarget:nil responseSelector:nil @@ -1232,11 +1242,14 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile } - (void)renameWindowWithId:(int)windowId - inSession:(NSString *)sessionName + inSessionNumber:(NSNumber *)sessionNumber toName:(NSString *)newName { NSString *theCommand; - if (sessionName) { - theCommand = [NSString stringWithFormat:@"rename-window -t \"%@:@%d\" \"%@\"", sessionName, windowId, [self stringByEscapingBackslashesAndRemovingNewlines:newName]]; + if (sessionNumber) { + theCommand = [NSString stringWithFormat:@"rename-window -t \"$%d:@%d\" \"%@\"", + sessionNumber.intValue, + windowId, + [self stringByEscapingBackslashesAndRemovingNewlines:newName]]; } else { theCommand = [NSString stringWithFormat:@"rename-window -t @%d \"%@\"", windowId, [self stringByEscapingBackslashesAndRemovingNewlines:newName]]; } @@ -1312,9 +1325,23 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile [self sendCommandToSetTabColors]; } +- (NSString *)encodedString:(NSString *)string prefix:(NSString *)prefix { + return [prefix stringByAppendingString:[[string dataUsingEncoding:NSUTF8StringEncoding] it_hexEncoded]]; +} + +- (NSString *)decodedString:(NSString *)string optionalPrefix:(NSString *)prefix { + if (![string hasPrefix:prefix]) { + return string; + } + return [[[NSString alloc] initWithData:[[string substringFromIndex:prefix.length] dataFromHexValues] + encoding:NSUTF8StringEncoding] autorelease]; +} + - (void)sendCommandToSetHotkeys { + NSString *hexEncoded = [self encodedString:[self.hotkeysString stringByEscapingQuotes] + prefix:iTermTmuxControllerEncodingPrefixHotkeys]; NSString *command = [NSString stringWithFormat:@"set -t $%d @hotkeys \"%@\"", - sessionId_, [self.hotkeysString stringByEscapingQuotes]]; + sessionId_, hexEncoded]; [gateway_ sendCommand:command responseTarget:nil responseSelector:nil @@ -1323,8 +1350,10 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile } - (void)sendCommandToSetTabColors { + NSString *command = [NSString stringWithFormat:@"set -t $%d @tab_colors \"%@\"", - sessionId_, [self.tabColorsString stringByEscapingQuotes]]; + sessionId_, [self encodedString:[self.tabColorsString stringByEscapingQuotes] + prefix:iTermTmuxControllerEncodingPrefixTabColors]]; [gateway_ sendCommand:command responseTarget:nil responseSelector:nil @@ -1376,7 +1405,8 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile - (void)breakOutWindowPane:(int)windowPane toTabAside:(NSString *)sibling { - [gateway_ sendCommand:[NSString stringWithFormat:@"break-pane -P -F \"#{window_id}\" %@ \"%%%d\"", [self breakPaneWindowPaneFlag], windowPane] + [gateway_ sendCommand:[NSString stringWithFormat:@"break-pane -P -F \"#{window_id}\" %@ \"%%%d\"", + [self breakPaneWindowPaneFlag], windowPane] responseTarget:self responseSelector:@selector(windowPaneBrokeOutWithWindowId:setAffinityTo:) responseObject:sibling @@ -1435,19 +1465,19 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile } - (void)linkWindowId:(int)windowId - inSession:(NSString *)sessionName - toSession:(NSString *)targetSession { - [gateway_ sendCommand:[NSString stringWithFormat:@"link-window -s \"%@:@%d\" -t \"%@:+\"", - sessionName, windowId, targetSession] + inSessionNumber:(int)sessionNumber + toSessionNumber:(int)targetSessionNumber { + [gateway_ sendCommand:[NSString stringWithFormat:@"link-window -s \"$%d:@%d\" -t \"$%d:+\"", + sessionNumber, windowId, targetSessionNumber] responseTarget:nil responseSelector:nil]; } - (void)moveWindowId:(int)windowId - inSession:(NSString *)sessionName - toSession:(NSString *)targetSession { - [gateway_ sendCommand:[NSString stringWithFormat:@"move-window -s \"%@:@%d\" -t \"%@:+\"", - sessionName, windowId, targetSession] + inSessionNumber:(int)sessionNumber + toSessionNumber:(int)targetSessionNumber { + [gateway_ sendCommand:[NSString stringWithFormat:@"move-window -s \"$%d:@%d\" -t \"$%d:+\"", + sessionNumber, windowId, targetSessionNumber] responseTarget:nil responseSelector:nil]; } @@ -1467,19 +1497,19 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile return pos; } -- (void)renameSession:(NSString *)oldName to:(NSString *)newName -{ - NSString *renameCommand = [NSString stringWithFormat:@"rename-session -t \"%@\" \"%@\"", - [oldName stringByEscapingQuotes], +- (void)renameSessionNumber:(int)sessionNumber + to:(NSString *)newName { + NSString *renameCommand = [NSString stringWithFormat:@"rename-session -t \"$%d\" \"%@\"", + sessionNumber, [newName stringByEscapingQuotes]]; [gateway_ sendCommand:renameCommand responseTarget:nil responseSelector:nil]; } -- (void)killSession:(NSString *)sessionName -{ - NSString *killCommand = [NSString stringWithFormat:@"kill-session -t \"%@\"", - [sessionName stringByEscapingQuotes]]; - [gateway_ sendCommand:killCommand responseTarget:nil responseSelector:nil]; +- (void)killSessionNumber:(int)sessionNumber { + NSString *killCommand = [NSString stringWithFormat:@"kill-session -t \"$%d\"", sessionNumber]; + [gateway_ sendCommand:killCommand + responseTarget:nil + responseSelector:nil]; [self listSessions]; } @@ -1493,44 +1523,46 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile [self listSessions]; } -- (void)attachToSession:(NSString *)sessionName -{ - NSString *attachCommand = [NSString stringWithFormat:@"attach-session -t \"%@\"", - [sessionName stringByEscapingQuotes]]; +- (void)attachToSessionWithNumber:(int)sessionNumber { + NSString *attachCommand = [NSString stringWithFormat:@"attach-session -t \"$%d\"", sessionNumber]; [gateway_ sendCommand:attachCommand responseTarget:nil responseSelector:nil]; } -- (void)listWindowsInSession:(NSString *)sessionName - target:(id)target - selector:(SEL)selector - object:(id)object { +- (void)listWindowsInSessionNumber:(int)sessionNumber + target:(id)target + selector:(SEL)selector + object:(id)object { if (detached_ || !object) { // This can happen if you're not attached to a session. return; } - NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@ -t \"%@\"", - kListWindowsFormat, sessionName]; + NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@ -t \"$%d\"", + kListWindowsFormat, sessionNumber]; // Wait a few seconds. We always get a windows-close notification when the last window in // a window closes. To avoid spamming the command line with list-windows, we wait a bit to see // if there is an exit notification coming down the pipe. const CGFloat kListWindowsDelay = 1.5; [listWindowsTimer_ invalidate]; - listWindowsTimer_ = [NSTimer scheduledTimerWithTimeInterval:kListWindowsDelay - target:self - selector:@selector(listWindowsTimerFired:) - userInfo:[NSArray arrayWithObjects:listWindowsCommand, object, target, NSStringFromSelector(selector), nil] - repeats:NO]; + listWindowsTimer_ = + [NSTimer scheduledTimerWithTimeInterval:kListWindowsDelay + target:self + selector:@selector(listWindowsTimerFired:) + userInfo:@[listWindowsCommand, + object, + target, + NSStringFromSelector(selector) ] + repeats:NO]; } - (void)listWindowsTimerFired:(NSTimer *)timer { NSArray *array = [timer userInfo]; - NSString *command = [array objectAtIndex:0]; - id object = [array objectAtIndex:1]; - id target = [array objectAtIndex:2]; - NSString *selector = [array objectAtIndex:3]; + NSString *command = array[0]; + id object = array[1]; + id target = array[2]; + NSString *selector = array[3]; [listWindowsTimer_ invalidate]; listWindowsTimer_ = nil; @@ -1538,10 +1570,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile [gateway_ sendCommand:command responseTarget:self responseSelector:@selector(didListWindows:userData:) - responseObject:[NSArray arrayWithObjects:object, - selector, - target, - nil] + responseObject:@[object, selector, target] flags:kTmuxGatewayCommandShouldTolerateErrors]; // Tolerates errors because the session may have been detached by the time we get the notification or the timer fires. } @@ -1549,8 +1578,10 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile { NSString *hidden = [[hiddenWindows_ allObjects] componentsJoinedByString:@","]; NSString *command = [NSString stringWithFormat: - @"set -t $%d @hidden \"%@\"", - sessionId_, hidden]; + @"set -t $%d @hidden \"%@\"", + sessionId_, + [self encodedString:hidden + prefix:iTermTmuxControllerEncodingPrefixHidden]]; [gateway_ sendCommand:command responseTarget:nil responseSelector:nil @@ -1594,7 +1625,9 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile NSString *enc = [maps componentsJoinedByString:@" "]; DLog(@"Save window origins to %@ called from %@", enc, [NSThread callStackSymbols]); NSString *command = [NSString stringWithFormat:@"set -t $%d @origins \"%@\"", - sessionId_, [enc stringByEscapingQuotes]]; + sessionId_, + [self encodedString:[enc stringByEscapingQuotes] + prefix:iTermTmuxControllerEncodingPrefixOrigins]]; if (!lastOrigins_ || ![command isEqualToString:lastOrigins_]) { [lastOrigins_ release]; lastOrigins_ = [command copy]; @@ -1603,7 +1636,8 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile responseTarget:self responseSelector:@selector(saveWindowOriginsResponse:)]; } - [self getOriginsResponse:enc]; + [self getOriginsResponse:[self encodedString:[enc stringByEscapingQuotes] + prefix:iTermTmuxControllerEncodingPrefixOrigins]]; } - (void)saveWindowOriginsResponse:(NSString *)response @@ -1651,7 +1685,8 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile // Update affinities if any have changed. NSString *arg = [affinities componentsJoinedByString:@" "]; NSString *command = [NSString stringWithFormat:@"set -t $%d @affinities \"%@\"", - sessionId_, [arg stringByEscapingQuotes]]; + sessionId_, [self encodedString:[arg stringByEscapingQuotes] + prefix:iTermTmuxControllerEncodingPrefixAffinities]]; if ([command isEqualToString:lastSaveAffinityCommand_]) { return; } @@ -1763,7 +1798,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile responseSelector:@selector(didSetLayout:) responseObject:nil flags:0], - [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"%@\"", sessionName_] + [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"$%d\"", sessionId_] responseTarget:self responseSelector:@selector(didListWindowsSubsequentToSettingLayout:) responseObject:nil @@ -1777,7 +1812,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile responseSelector:@selector(didSetLayout:) responseObject:nil flags:0], - [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"%@\"", sessionName_] + [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"$%d\"", sessionId_] responseTarget:self responseSelector:@selector(didListWindowsSubsequentToSettingLayout:) responseObject:nil @@ -1798,31 +1833,32 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile #pragma mark - Private -- (void)getOriginsResponse:(NSString *)result -{ - [origins_ removeAllObjects]; - if ([result length] > 0) { - NSArray *windows = [result componentsSeparatedByString:@" "]; - for (NSString *wstr in windows) { - NSArray *tuple = [wstr componentsSeparatedByString:@":"]; - if (tuple.count != 2) { - continue; - } - NSString *windowsStr = [tuple objectAtIndex:0]; - NSString *coords = [tuple objectAtIndex:1]; - NSArray *windowIds = [windowsStr componentsSeparatedByString:@","]; - NSArray *xy = [coords componentsSeparatedByString:@","]; - if (xy.count != 2) { - continue; - } - NSPoint origin = NSMakePoint([[xy objectAtIndex:0] intValue], - [[xy objectAtIndex:1] intValue]); - for (NSString *wid in windowIds) { - [origins_ setObject:[NSValue valueWithPoint:origin] - forKey:[NSNumber numberWithInt:[wid intValue]]]; - } - } - } +- (void)getOriginsResponse:(NSString *)encodedResult { + NSString *result = [self decodedString:encodedResult + optionalPrefix:iTermTmuxControllerEncodingPrefixOrigins]; + [origins_ removeAllObjects]; + if ([result length] > 0) { + NSArray *windows = [result componentsSeparatedByString:@" "]; + for (NSString *wstr in windows) { + NSArray *tuple = [wstr componentsSeparatedByString:@":"]; + if (tuple.count != 2) { + continue; + } + NSString *windowsStr = [tuple objectAtIndex:0]; + NSString *coords = [tuple objectAtIndex:1]; + NSArray *windowIds = [windowsStr componentsSeparatedByString:@","]; + NSArray *xy = [coords componentsSeparatedByString:@","]; + if (xy.count != 2) { + continue; + } + NSPoint origin = NSMakePoint([[xy objectAtIndex:0] intValue], + [[xy objectAtIndex:1] intValue]); + for (NSString *wid in windowIds) { + [origins_ setObject:[NSValue valueWithPoint:origin] + forKey:[NSNumber numberWithInt:[wid intValue]]]; + } + } + } } - (NSString *)shortStringForHotkeyDictionary:(NSDictionary *)dict paneID:(int)wp { @@ -1851,7 +1887,8 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile return [parts componentsJoinedByString:@" "]; } -- (void)getHotkeysResponse:(NSString *)result { +- (void)getHotkeysResponse:(NSString *)encodedResult { + NSString *result = [self decodedString:encodedResult optionalPrefix:iTermTmuxControllerEncodingPrefixHotkeys]; [_hotkeys removeAllObjects]; if (result.length > 0) { [_hotkeys removeAllObjects]; @@ -1870,7 +1907,9 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile } } -- (void)getTabColorsResponse:(NSString *)result { +- (void)getTabColorsResponse:(NSString *)encodedResult { + NSString *result = [self decodedString:encodedResult + optionalPrefix:iTermTmuxControllerEncodingPrefixTabColors]; [_tabColors removeAllObjects]; if (result.length > 0) { [_tabColors removeAllObjects]; @@ -1903,14 +1942,15 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile response = @""; } TSVDocument *doc = [response tsvDocumentWithFields:[self listWindowFields]]; - id object = [userData objectAtIndex:0]; - SEL selector = NSSelectorFromString([userData objectAtIndex:1]); - id target = [userData objectAtIndex:2]; + id object = userData[0]; + SEL selector = NSSelectorFromString(userData[1]); + id target = userData[2]; [target performSelector:selector withObject:doc withObject:object]; } -- (void)getHiddenWindowsResponse:(NSString *)response -{ +- (void)getHiddenWindowsResponse:(NSString *)encodedResponse { + NSString *response = [self decodedString:encodedResponse + optionalPrefix:iTermTmuxControllerEncodingPrefixHidden]; [hiddenWindows_ removeAllObjects]; if ([response length] > 0) { NSArray *windowIds = [response componentsSeparatedByString:@","]; @@ -1922,7 +1962,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile } - (void)getAffinitiesResponse:(NSString *)result { - [self setAffinitiesFromString:result]; + [self setAffinitiesFromString:[self decodedString:result optionalPrefix:iTermTmuxControllerEncodingPrefixAffinities]]; } - (NSArray *)componentsOfAffinities:(NSString *)affinities { @@ -1991,9 +2031,28 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile - (void)listSessionsResponse:(NSString *)result { - self.sessions = [result componentsSeparatedByRegex:@"\n"]; + self.sessionObjects = [[result componentsSeparatedByRegex:@"\n"] mapWithBlock:^iTermTmuxSessionObject *(NSString *line) { + const NSInteger space = [line rangeOfString:@" "].location; + if (space == NSNotFound) { + return nil; + } + NSString *sessionID = [line substringToIndex:space]; + NSString *sessionName = [line substringFromIndex:space + 1]; + if (![sessionID hasPrefix:@"$"]) { + return nil; + } + NSScanner *scanner = [NSScanner scannerWithString:[sessionID substringFromIndex:1]]; + int sessionNumber = -1; + if (![scanner scanInt:&sessionNumber] || sessionNumber < 0) { + return nil; + } + iTermTmuxSessionObject *obj = [[[iTermTmuxSessionObject alloc] init] autorelease]; + obj.name = sessionName; + obj.number = sessionNumber; + return obj; + }]; [[NSNotificationCenter defaultCenter] postNotificationName:kTmuxControllerSessionsDidChange - object:self.sessions]; + object:nil]; } - (void)listedWindowsToOpenOne:(NSString *)response diff --git sources/TmuxDashboardController.m sources/TmuxDashboardController.m index b83bfe6..b2ed7b7 100644 --- sources/TmuxDashboardController.m +++ sources/TmuxDashboardController.m @@ -49,7 +49,7 @@ if (self) { [self window]; - [sessionsTable_ selectSessionWithName:[[self tmuxController] sessionName]]; + [sessionsTable_ selectSessionNumber:[[self tmuxController] sessionId]]; [self reloadWindows]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tmuxControllerDetached:) @@ -140,78 +140,79 @@ #pragma mark TmuxSessionsTableProtocol -- (void)renameSessionWithName:(NSString *)oldName toName:(NSString *)newName -{ - [[self tmuxController] renameSession:oldName to:newName]; +- (void)renameSessionWithNumber:(int)sessionNumber toName:(NSString *)newName { + [[self tmuxController] renameSessionNumber:sessionNumber + to:newName]; } -- (void)removeSessionWithName:(NSString *)sessionName -{ - [[self tmuxController] killSession:sessionName]; +- (void)removeSessionWithNumber:(int)sessionNumber { + [[self tmuxController] killSessionNumber:sessionNumber]; } -- (void)addSessionWithName:(NSString *)sessionName -{ +- (void)addSessionWithName:(NSString *)sessionName { [[self tmuxController] addSessionWithName:sessionName]; } -- (void)attachToSessionWithName:(NSString *)sessionName -{ - [[self tmuxController] attachToSession:sessionName]; +- (void)attachToSessionWithNumber:(int)sessionNumber { + [[self tmuxController] attachToSessionWithNumber:sessionNumber]; } -- (void)detach -{ +- (void)detach { [[self tmuxController] requestDetach]; } -- (NSString *)nameOfAttachedSession -{ - return [[self tmuxController] sessionName]; +- (NSNumber *)numberOfAttachedSession { + TmuxController *controller = [self tmuxController]; + if (!controller) { + return nil; + } + return @([controller sessionId]); } -- (NSArray *)sessions -{ - return [[self tmuxController] sessions]; +- (NSArray *)sessionsTableModelValues:(id)sender { + return [self.tmuxController sessionObjects]; } -- (void)selectedSessionChangedTo:(NSString *)newSessionName -{ +- (NSArray *)sessionsTableObjects:(TmuxSessionsTable *)sender { + return [[self tmuxController] sessionObjects]; +} + +- (void)selectedSessionDidChange { [windowsTable_ setWindows:[NSArray array]]; [self reloadWindows]; } - (void)linkWindowId:(int)windowId - inSession:(NSString *)sessionName - toSession:(NSString *)targetSession -{ + inSessionNumber:(int)sourceSessionNumber + toSessionNumber:(int)targetSessionNumber { [[self tmuxController] linkWindowId:windowId - inSession:sessionName - toSession:targetSession]; + inSessionNumber:sourceSessionNumber + toSessionNumber:targetSessionNumber]; } - (void)moveWindowId:(int)windowId - inSession:(NSString *)sessionName - toSession:(NSString *)targetSession -{ + inSessionNumber:(int)sessionNumber + toSessionNumber:(int)targetSessionNumber { [[self tmuxController] moveWindowId:windowId - inSession:sessionName - toSession:targetSession]; + inSessionNumber:sessionNumber + toSessionNumber:targetSessionNumber]; } #pragma mark TmuxWindowsTableProtocol -- (void)reloadWindows -{ - [[self tmuxController] listWindowsInSession:[sessionsTable_ selectedSessionName] - target:self - selector:@selector(setWindows:forSession:) - object:[sessionsTable_ selectedSessionName]]; +- (void)reloadWindows { + NSNumber *sessionNumber = [sessionsTable_ selectedSessionNumber]; + if (!sessionNumber) { + return; + } + [[self tmuxController] listWindowsInSessionNumber:sessionNumber.intValue + target:self + selector:@selector(setWindows:forSession:) + object:[sessionsTable_ selectedSessionNumber]]; } -- (void)setWindows:(TSVDocument *)doc forSession:(NSString *)sessionName -{ - if ([sessionName isEqualToString:[sessionsTable_ selectedSessionName]]) { +- (void)setWindows:(TSVDocument *)doc forSession:(NSNumber *)sessionNumber { + if ([sessionNumber isEqual:[sessionsTable_ selectedSessionNumber]]) { NSMutableArray *windows = [NSMutableArray array]; for (NSArray *record in doc.records) { [windows addObject:[NSMutableArray arrayWithObjects: @@ -223,18 +224,19 @@ } } -- (void)renameWindowWithId:(int)windowId toName:(NSString *)newName -{ +- (void)renameWindowWithId:(int)windowId toName:(NSString *)newName { + NSNumber *sessionNumber = [sessionsTable_ selectedSessionNumber]; + if (!sessionNumber) { + return; + } [[self tmuxController] renameWindowWithId:windowId - inSession:[sessionsTable_ selectedSessionName] + inSessionNumber:sessionNumber toName:newName]; [self reloadWindows]; } -- (void)unlinkWindowWithId:(int)windowId -{ - [[self tmuxController] unlinkWindowWithId:windowId - inSession:[sessionsTable_ selectedSessionName]]; +- (void)unlinkWindowWithId:(int)windowId { + [[self tmuxController] unlinkWindowWithId:windowId]; [self reloadWindows]; } @@ -242,15 +244,14 @@ NSString *lastName = [[windowsTable_ names] lastObject]; if (lastName) { TmuxController *tmuxController = self.tmuxController; - [tmuxController newWindowInSession:[sessionsTable_ selectedSessionName] - scope:[iTermVariableScope globalsScope] - initialDirectory:[iTermInitialDirectory initialDirectoryFromProfile:tmuxController.sharedProfile - objectType:iTermWindowObject]]; + [tmuxController newWindowInSessionNumber:[sessionsTable_ selectedSessionNumber] + scope:[iTermVariableScope globalsScope] + initialDirectory:[iTermInitialDirectory initialDirectoryFromProfile:tmuxController.sharedProfile + objectType:iTermWindowObject]]; } } -- (void)showWindowsWithIds:(NSArray *)windowIds inTabs:(BOOL)inTabs -{ +- (void)showWindowsWithIds:(NSArray *)windowIds inTabs:(BOOL)inTabs { if (inTabs) { for (NSNumber *wid in windowIds) { [[self tmuxController] openWindowWithId:[wid intValue] @@ -265,27 +266,24 @@ profile:self.tmuxController.sharedProfile]; } } - [[self tmuxController] saveHiddenWindows]; + [[self tmuxController] saveHiddenWindows]; } - (void)hideWindowWithId:(int)windowId { - [[self tmuxController] hideWindow:windowId]; + [[self tmuxController] hideWindow:windowId]; [windowsTable_ updateEnabledStateOfButtons]; } -- (BOOL)haveSelectedSession -{ - return [sessionsTable_ selectedSessionName] != nil; +- (BOOL)haveSelectedSession { + return [sessionsTable_ selectedSessionNumber] != nil; } -- (BOOL)currentSessionSelected -{ - return [[sessionsTable_ selectedSessionName] isEqualToString:[[self tmuxController] sessionName]]; +- (BOOL)currentSessionSelected { + return [[sessionsTable_ selectedSessionNumber] isEqual:@([[self tmuxController] sessionId])]; } -- (BOOL)haveOpenWindowWithId:(int)windowId -{ +- (BOOL)haveOpenWindowWithId:(int)windowId { return [[self tmuxController] window:windowId] != nil; } @@ -294,48 +292,41 @@ [tab.activeSession reveal]; } -- (NSString *)selectedSessionName -{ - return [sessionsTable_ selectedSessionName]; +- (NSNumber *)selectedSessionNumber { + return [sessionsTable_ selectedSessionNumber]; } #pragma mark - Private -- (void)tmuxControllerDetached:(NSNotification *)notification -{ - [sessionsTable_ setSessions:[NSArray array]]; +- (void)tmuxControllerDetached:(NSNotification *)notification { + [sessionsTable_ setSessionObjects:@[]]; } -- (void)tmuxControllerSessionsDidChange:(NSNotification *)notification -{ - [sessionsTable_ setSessions:[[self tmuxController] sessions]]; +- (void)tmuxControllerSessionsDidChange:(NSNotification *)notification { + [sessionsTable_ setSessionObjects:[[self tmuxController] sessionObjects]]; } -- (void)tmuxControllerWindowsDidChange:(NSNotification *)notification -{ +- (void)tmuxControllerWindowsDidChange:(NSNotification *)notification { if ([[self window] isVisible]) { [self reloadWindows]; } } -- (void)tmuxControllerAttachedSessionChanged:(NSNotification *)notification -{ +- (void)tmuxControllerAttachedSessionChanged:(NSNotification *)notification { if ([[self window] isVisible]) { - [sessionsTable_ selectSessionWithName:[[self tmuxController] sessionName]]; + [sessionsTable_ selectSessionNumber:[[self tmuxController] sessionId]]; [windowsTable_ updateEnabledStateOfButtons]; } } -- (void)tmuxControllerWindowOpenedOrClosed:(NSNotification *)notification -{ +- (void)tmuxControllerWindowOpenedOrClosed:(NSNotification *)notification { if ([[self window] isVisible]) { [windowsTable_ updateEnabledStateOfButtons]; [windowsTable_ reloadData]; } } -- (void)tmuxControllerWindowWasRenamed:(NSNotification *)notification -{ +- (void)tmuxControllerWindowWasRenamed:(NSNotification *)notification { if ([[self window] isVisible]) { NSArray *objects = [notification object]; int wid = [[objects objectAtIndex:0] intValue]; @@ -344,8 +335,7 @@ } } -- (void)tmuxControllerSessionWasRenamed:(NSNotification *)notification -{ +- (void)tmuxControllerSessionWasRenamed:(NSNotification *)notification { // This is a bit of extra work but the sessions table wasn't built knowing about session IDs. [[self tmuxController] listSessions]; } @@ -364,8 +354,7 @@ [self connectionSelectionDidChange:nil]; } -- (TmuxController *)tmuxController -{ +- (TmuxController *)tmuxController { return [[TmuxControllerRegistry sharedInstance] controllerForClient:[self currentClient]]; // TODO: track the current client when multiples are supported } @@ -375,7 +364,7 @@ - (IBAction)connectionSelectionDidChange:(id)sender { - [sessionsTable_ setSessions:[[self tmuxController] sessions]]; + [sessionsTable_ setSessionObjects:[[self tmuxController] sessionObjects]]; [self reloadWindows]; } diff --git sources/TmuxSessionsTable.h sources/TmuxSessionsTable.h index 856f0f5..a3e5823 100644 --- sources/TmuxSessionsTable.h +++ sources/TmuxSessionsTable.h @@ -9,21 +9,25 @@ #import #import "FutureMethods.h" +@class TmuxSessionsTable; +@class iTermTmuxSessionObject; + @protocol TmuxSessionsTableProtocol -- (NSArray *)sessions; -- (void)renameSessionWithName:(NSString *)oldName toName:(NSString *)newName; -- (void)removeSessionWithName:(NSString *)sessionName; +- (NSArray *)sessionsTableObjects:(TmuxSessionsTable *)sender; +- (void)renameSessionWithNumber:(int)sessionNumber + toName:(NSString *)newName; +- (void)removeSessionWithNumber:(int)sessionNumber; - (void)addSessionWithName:(NSString *)sessionName; -- (void)attachToSessionWithName:(NSString *)sessionName; -- (NSString *)nameOfAttachedSession; -- (void)selectedSessionChangedTo:(NSString *)newName; +- (void)attachToSessionWithNumber:(int)sessionNumber; +- (NSNumber *)numberOfAttachedSession; +- (void)selectedSessionDidChange; - (void)linkWindowId:(int)windowId - inSession:(NSString *)sessionName - toSession:(NSString *)targetSession; + inSessionNumber:(int)sourceSessionNumber + toSessionNumber:(int)targetSessionNumber; - (void)moveWindowId:(int)windowId - inSession:(NSString *)sessionName - toSession:(NSString *)targetSession; + inSessionNumber:(int)sessionNumber + toSessionNumber:(int)targetSessionNumber; - (void)detach; @end @@ -31,9 +35,9 @@ @interface TmuxSessionsTable : NSObject @property(nonatomic, assign) id delegate; -@property(nonatomic, readonly) NSString *selectedSessionName; +@property(nonatomic, readonly) NSNumber *selectedSessionNumber; -- (void)setSessions:(NSArray *)names; -- (void)selectSessionWithName:(NSString *)name; +- (void)setSessionObjects:(NSArray *)names; +- (void)selectSessionNumber:(int)number; @end diff --git sources/TmuxSessionsTable.m sources/TmuxSessionsTable.m index f9a086f..88e2f72 100644 --- sources/TmuxSessionsTable.m +++ sources/TmuxSessionsTable.m @@ -7,12 +7,15 @@ // #import "TmuxSessionsTable.h" + #import "FutureMethods.h" +#import "iTermTmuxSessionObject.h" +#import "NSArray+iTerm.h" extern NSString *kWindowPasteboardType; @implementation TmuxSessionsTable { - NSMutableArray *model_; + NSMutableArray *_model; BOOL canAttachToSelectedSession_; IBOutlet NSTableColumn *checkColumn_; @@ -28,7 +31,7 @@ extern NSString *kWindowPasteboardType; - (instancetype)init { self = [super init]; if (self) { - model_ = [[NSMutableArray alloc] init]; + _model = [[NSMutableArray alloc] init]; } return self; } @@ -39,27 +42,27 @@ extern NSString *kWindowPasteboardType; [tableView_ setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleRegular]; } -- (void)dealloc -{ - [model_ release]; +- (void)dealloc { + [_model release]; [super dealloc]; } - (void)setDelegate:(id)delegate { delegate_ = delegate; - [self setSessions:[delegate_ sessions]]; + [self setSessionObjects:[delegate_ sessionsTableObjects:self]]; } -- (void)setSessions:(NSArray *)names +- (void)setSessionObjects:(NSArray *)sessions { - [model_ removeAllObjects]; - [model_ addObjectsFromArray:names]; + [_model removeAllObjects]; + [_model addObjectsFromArray:sessions]; [tableView_ reloadData]; } -- (void)selectSessionWithName:(NSString *)name -{ - NSUInteger i = [model_ indexOfObject:name]; +- (void)selectSessionNumber:(int)number { + NSInteger i = [_model indexOfObjectPassingTest:^BOOL(iTermTmuxSessionObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + return obj.number == number; + }]; if (i != NSNotFound) { [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO]; @@ -74,47 +77,45 @@ extern NSString *kWindowPasteboardType; - (IBAction)removeSession:(id)sender { - NSString *name = [self selectedSessionName]; - if (name) { - [delegate_ removeSessionWithName:name]; + NSNumber *number = [self selectedSessionNumber]; + if (number) { + [delegate_ removeSessionWithNumber:number.intValue]; } } - (IBAction)attach:(id)sender { - NSString *name = [self selectedSessionName]; - if (name) { - [delegate_ attachToSessionWithName:name]; + NSNumber *number = [self selectedSessionNumber]; + if (number) { + [delegate_ attachToSessionWithNumber:number.intValue]; } } - (IBAction)detach:(id)sender { - NSString *name = [self selectedSessionName]; - if (name) { + NSNumber *number = [self selectedSessionNumber]; + if (number) { [delegate_ detach]; } } #pragma mark NSTableViewDataSource -- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView -{ - return model_.count; +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { + return _model.count; } - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn - row:(NSInteger)rowIndex -{ - NSString *name = [model_ objectAtIndex:rowIndex]; + row:(NSInteger)rowIndex { + iTermTmuxSessionObject *sessionObject = _model[rowIndex]; if (aTableColumn == checkColumn_) { - if ([[delegate_ nameOfAttachedSession] isEqualToString:name]) { + if ([[delegate_ numberOfAttachedSession] isEqual:@(sessionObject.number)]) { return @"✓"; } else { return @""; } } else { - if (rowIndex < model_.count) { - return name; + if (rowIndex < _model.count) { + return sessionObject.name; } else { return nil; } @@ -124,10 +125,9 @@ extern NSString *kWindowPasteboardType; - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn - row:(NSInteger)rowIndex -{ - [delegate_ renameSessionWithName:[model_ objectAtIndex:rowIndex] - toName:(NSString *)anObject]; + row:(NSInteger)rowIndex { + [delegate_ renameSessionWithNumber:_model[rowIndex].number + toName:(NSString *)anObject]; } #pragma mark NSTableViewDataSource @@ -138,16 +138,15 @@ extern NSString *kWindowPasteboardType; return YES; } -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification -{ +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification { [self updateEnabledStateOfButtons]; - [delegate_ selectedSessionChangedTo:[self selectedSessionName]]; + [delegate_ selectedSessionDidChange]; } -- (NSString *)selectedSessionName { +- (NSNumber *)selectedSessionNumber { int i = [tableView_ selectedRow]; - if (i >= 0 && i < model_.count) { - return [model_ objectAtIndex:i]; + if (i >= 0 && i < _model.count) { + return @(_model[i].number); } else { return nil; } @@ -159,19 +158,19 @@ extern NSString *kWindowPasteboardType; dropOperation:(NSTableViewDropOperation)operation { NSPasteboard *pb = [info draggingPasteboard]; NSArray* pair = [pb propertyListForType:kWindowPasteboardType]; - NSString *sessionName = [pair objectAtIndex:0]; - NSArray *draggedItems = [pair objectAtIndex:1]; - NSString *targetSession = [model_ objectAtIndex:row]; + NSNumber *sessionNumber = pair[0]; + NSArray *draggedItems = pair[1]; + iTermTmuxSessionObject *targetSessionObject = _model[row]; for (NSArray *tuple in draggedItems) { NSNumber *windowId = [tuple objectAtIndex:1]; if (info.draggingSourceOperationMask & NSDragOperationLink) { [delegate_ linkWindowId:[windowId intValue] - inSession:sessionName - toSession:targetSession]; + inSessionNumber:sessionNumber.intValue + toSessionNumber:targetSessionObject.number]; } else { [delegate_ moveWindowId:[windowId intValue] - inSession:sessionName - toSession:targetSession]; + inSessionNumber:sessionNumber.intValue + toSessionNumber:targetSessionObject.number]; } } return YES; @@ -205,11 +204,16 @@ extern NSString *kWindowPasteboardType; } } -- (NSString *)nameForNewSession -{ +- (BOOL)haveSessionWithName:(NSString *)name { + return [_model anyWithBlock:^BOOL(iTermTmuxSessionObject *anObject) { + return [anObject.name isEqualToString:name]; + }]; +} + +- (NSString *)nameForNewSession { int n = 0; NSString *candidate = [self nameForNewSessionWithNumber:n]; - while ([model_ indexOfObject:candidate] != NSNotFound) { + while ([self haveSessionWithName:candidate]) { n++; candidate = [self nameForNewSessionWithNumber:n]; } @@ -223,7 +227,9 @@ extern NSString *kWindowPasteboardType; [detachButton_ setEnabled:NO]; [removeButton_ setEnabled:NO]; } else { - BOOL isAttachedSession = [[delegate_ nameOfAttachedSession] isEqualToString:[self selectedSessionName]]; + NSNumber *selected = [self selectedSessionNumber]; + BOOL isAttachedSession = (selected != nil && + [[delegate_ numberOfAttachedSession] isEqual:@(selected.intValue)]); [attachButton_ setEnabled:!isAttachedSession]; [detachButton_ setEnabled:isAttachedSession]; [removeButton_ setEnabled:YES]; diff --git sources/TmuxWindowsTable.h sources/TmuxWindowsTable.h index 7c3e16c..6e9604e 100644 --- sources/TmuxWindowsTable.h +++ sources/TmuxWindowsTable.h @@ -22,7 +22,7 @@ extern NSString *kWindowPasteboardType; - (BOOL)haveSelectedSession; - (BOOL)currentSessionSelected; - (BOOL)haveOpenWindowWithId:(int)windowId; -- (NSString *)selectedSessionName; +- (NSNumber *)selectedSessionNumber; - (void)tmuxWindowsTableDidSelectWindowWithId:(int)windowId; @end diff --git sources/TmuxWindowsTable.m sources/TmuxWindowsTable.m index 56f4954..8f92993 100644 --- sources/TmuxWindowsTable.m +++ sources/TmuxWindowsTable.m @@ -173,9 +173,9 @@ NSString *kWindowPasteboardType = @"kWindowPasteboardType"; - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn - row:(NSInteger)rowIndex -{ - [delegate_ renameWindowWithId:[[[[self filteredModel] objectAtIndex:rowIndex] objectAtIndex:1] intValue] + row:(NSInteger)rowIndex { + const int windowID = [[[[self filteredModel] objectAtIndex:rowIndex] objectAtIndex:1] intValue]; + [delegate_ renameWindowWithId:windowID toName:anObject]; } @@ -200,7 +200,7 @@ NSString *kWindowPasteboardType = @"kWindowPasteboardType"; NSArray* selectedItems = [[self filteredModel] objectsAtIndexes:rowIndexes]; [pboard declareTypes:[NSArray arrayWithObject:kWindowPasteboardType] owner:self]; [pboard setPropertyList:[NSArray arrayWithObjects: - [delegate_ selectedSessionName], + [delegate_ selectedSessionNumber], selectedItems, nil] forType:kWindowPasteboardType]; @@ -219,14 +219,13 @@ NSString *kWindowPasteboardType = @"kWindowPasteboardType"; #pragma mark - Private -- (NSArray *)selectedWindowIdsAsStrings -{ - NSArray *ids = [self selectedWindowIds]; +- (NSArray *)selectedWindowIdsAsStrings { + NSArray *ids = [self selectedWindowIds]; NSMutableArray *result = [NSMutableArray array]; - for (NSString *n in ids) { - [result addObject:n]; - } - return result; + for (NSString *n in ids) { + [result addObject:n]; + } + return result; } - (NSArray *)selectedWindowIds diff --git sources/iTermInitialDirectory+Tmux.h sources/iTermInitialDirectory+Tmux.h index a470a56..586ade6 100644 --- sources/iTermInitialDirectory+Tmux.h +++ sources/iTermInitialDirectory+Tmux.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface iTermInitialDirectory (Tmux) -- (void)tmuxNewWindowCommandInSession:(nullable NSString *)session +- (void)tmuxNewWindowCommandInSessionNumber:(nullable NSNumber *)sessionNumber recyclingSupported:(BOOL)recyclingSupported scope:(iTermVariableScope *)scope completion:(void (^)(NSString *))completion; diff --git sources/iTermInitialDirectory+Tmux.m sources/iTermInitialDirectory+Tmux.m index a67bea4..3259eda 100644 --- sources/iTermInitialDirectory+Tmux.m +++ sources/iTermInitialDirectory+Tmux.m @@ -11,14 +11,14 @@ @implementation iTermInitialDirectory(Tmux) -- (void)tmuxNewWindowCommandInSession:(NSString *)session - recyclingSupported:(BOOL)recyclingSupported - scope:(iTermVariableScope *)scope - completion:(void (^)(NSString *))completion { +- (void)tmuxNewWindowCommandInSessionNumber:(nullable NSNumber *)sessionNumber + recyclingSupported:(BOOL)recyclingSupported + scope:(iTermVariableScope *)scope + completion:(void (^)(NSString *))completion { NSArray *args = @[ @"new-window", @"-PF '#{window_id}'" ]; - if (session) { - NSString *targetSessionArg = [NSString stringWithFormat:@"\"%@:+\"", [session stringByEscapingQuotes]]; + if (sessionNumber) { + NSString *targetSessionArg = [NSString stringWithFormat:@"\"$%d:+\"", sessionNumber.intValue]; NSArray *insertionArguments = @[ @"-a", @"-t", targetSessionArg ]; @@ -33,10 +33,10 @@ - (void)tmuxNewWindowCommandRecyclingSupported:(BOOL)recyclingSupported scope:(iTermVariableScope *)scope completion:(void (^)(NSString *))completion { - [self tmuxNewWindowCommandInSession:nil - recyclingSupported:recyclingSupported - scope:scope - completion:completion]; + [self tmuxNewWindowCommandInSessionNumber:nil + recyclingSupported:recyclingSupported + scope:scope + completion:completion]; } - (void)tmuxSplitWindowCommand:(int)wp diff --git sources/iTermTmuxOptionMonitor.h sources/iTermTmuxOptionMonitor.h index 147fcd6..8dffe52 100644 --- sources/iTermTmuxOptionMonitor.h +++ sources/iTermTmuxOptionMonitor.h @@ -18,8 +18,12 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, strong) iTermVariableScope *scope; - (instancetype)init NS_UNAVAILABLE; + +// If `fallbackVariableName` is nonnil, the value of the variable named +// `fallbackVariableName` will be used for tmux 2.8 and earlier. - (instancetype)initWithGateway:(TmuxGateway *)gateway scope:(iTermVariableScope *)scope + fallbackVariableName:(nullable NSString *)fallbackVariableName format:(NSString *)format target:(NSString *)tmuxTarget variableName:(nullable NSString *)variableName diff --git sources/iTermTmuxOptionMonitor.m sources/iTermTmuxOptionMonitor.m index 4f72d02..fd6120f 100644 --- sources/iTermTmuxOptionMonitor.m +++ sources/iTermTmuxOptionMonitor.m @@ -10,6 +10,7 @@ #import "DebugLogging.h" #import "iTermVariables.h" #import "iTermVariableScope+Session.h" +#import "NSStringITerm.h" #import "NSTimer+iTerm.h" #import "TmuxGateway.h" @@ -19,11 +20,13 @@ BOOL _haveOutstandingRequest; NSString *_target; NSString *_variableName; + NSString *_fallbackVariableName; void (^_block)(NSString *); } - (instancetype)initWithGateway:(TmuxGateway *)gateway scope:(iTermVariableScope *)scope + fallbackVariableName:(NSString *)fallbackVariableName format:(NSString *)format target:(NSString *)target variableName:(NSString *)variableName @@ -36,6 +39,7 @@ _target = [target copy]; _variableName = [variableName copy]; _block = [block copy]; + _fallbackVariableName = [fallbackVariableName copy]; } return self; } @@ -64,13 +68,21 @@ [self updateOnce]; } +- (NSString *)command { + return [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat]; +} + - (void)updateOnce { if (_haveOutstandingRequest) { DLog(@"Not making a request because one is outstanding"); return; } + if (_fallbackVariableName && self.gateway.minimumServerVersion.doubleValue <= 2.9) { + [self didFetch:[self.scope valueForVariableName:_fallbackVariableName]]; + return; + } _haveOutstandingRequest = YES; - NSString *command = [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat]; + NSString *command = [self command]; DLog(@"Request option with command %@", command); [self.gateway sendCommand:command responseTarget:self @@ -80,7 +92,7 @@ } - (void)didFetch:(NSString *)value { - DLog(@"Did fetch %@", value); + DLog(@"%@ -> %@", self.command, value); if (!value) { // Probably the pane went away and we'll be dealloced soon. return; diff --git sources/iTermTmuxSessionObject.h sources/iTermTmuxSessionObject.h new file mode 100644 index 0000000..6407d5f --- /dev/null +++ sources/iTermTmuxSessionObject.h @@ -0,0 +1,17 @@ +// +// iTermTmuxSessionObject.h +// iTerm2SharedARC +// +// Created by George Nachman on 9/25/19. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface iTermTmuxSessionObject : NSObject +@property (nonatomic, copy) NSString *name; +@property (nonatomic) int number; +@end + +NS_ASSUME_NONNULL_END diff --git sources/iTermTmuxSessionObject.m sources/iTermTmuxSessionObject.m new file mode 100644 index 0000000..5ac9130 --- /dev/null +++ sources/iTermTmuxSessionObject.m @@ -0,0 +1,12 @@ +// +// iTermTmuxSessionObject.m +// iTerm2SharedARC +// +// Created by George Nachman on 9/25/19. +// + +#import "iTermTmuxSessionObject.h" + +@implementation iTermTmuxSessionObject + +@end diff --git sources/iTermTmuxStatusBarMonitor.h sources/iTermTmuxStatusBarMonitor.h index 8172779..96338ec 100644 --- sources/iTermTmuxStatusBarMonitor.h +++ sources/iTermTmuxStatusBarMonitor.h @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN +// NOTE: Requires tmux 2.9 @interface iTermTmuxStatusBarMonitor : NSObject @property (nonatomic) BOOL active; diff --git sources/iTermTmuxStatusBarMonitor.m sources/iTermTmuxStatusBarMonitor.m index 50504da..0d3daf3 100644 --- sources/iTermTmuxStatusBarMonitor.m +++ sources/iTermTmuxStatusBarMonitor.m @@ -9,6 +9,7 @@ #import "DebugLogging.h" #import "iTermVariableScope.h" +#import "NSStringITerm.h" #import "NSTimer+iTerm.h" #import "TmuxGateway.h" #import "RegexKitLite.h" @@ -49,13 +50,8 @@ - (void)requestUpdates { _accelerated = NO; - [_gateway sendCommand:@"display-message -p \"#{status-left}\"" responseTarget:self responseSelector:@selector(handleStatusLeftResponse:)]; - [_gateway sendCommand:@"display-message -p \"#{status-right}\"" responseTarget:self responseSelector:@selector(handleStatusRightResponse:)]; -} - -- (NSString *)escapedString:(NSString *)string { - return [[string stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] - stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; + [_gateway sendCommand:@"display-message -p \"#{T:status-left}\"" responseTarget:self responseSelector:@selector(handleStatusLeftValueExpansionResponse:)]; + [_gateway sendCommand:@"display-message -p \"#{T:status-right}\"" responseTarget:self responseSelector:@selector(handleStatusRightValueExpansionResponse:)]; } - (void)handleStatusIntervalResponse:(NSString *)response { @@ -66,28 +62,14 @@ _timer = [NSTimer scheduledWeakTimerWithTimeInterval:interval target:self selector:@selector(timerDidFire:) userInfo:nil repeats:YES]; } -- (void)handleStatusLeftResponse:(NSString *)response { - if (!response) { - return; - } - NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]]; - [_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusLeftValueExpansionResponse:)]; -} - -- (void)handleStatusRightResponse:(NSString *)response { - if (!response) { - return; - } - NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]]; - [_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusRightValueExpansionResponse:)]; -} - - (void)handleStatusLeftValueExpansionResponse:(NSString *)string { + DLog(@"Left status bar is: %@", string); [self.scope setValue:[self sanitizedString:string] ?: @"" forVariableNamed:iTermVariableKeySessionTmuxStatusLeft]; [self accelerateUpdateIfStringContainsNotReady:string]; } - (void)handleStatusRightValueExpansionResponse:(NSString *)string { + DLog(@"Right status bar is: %@", string); [self.scope setValue:[self sanitizedString:string] ?: @"" forVariableNamed:iTermVariableKeySessionTmuxStatusRight]; [self accelerateUpdateIfStringContainsNotReady:string]; } diff --git sources/iTermWorkingDirectoryPoller.m sources/iTermWorkingDirectoryPoller.m index 5271227..f4d8602 100644 --- sources/iTermWorkingDirectoryPoller.m +++ sources/iTermWorkingDirectoryPoller.m @@ -37,6 +37,7 @@ __weak __typeof(self) weakSelf = self; _tmuxOptionMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:gateway scope:scope + fallbackVariableName:nil format:@"#{pane_current_path}" target:[NSString stringWithFormat:@"%%%@", @(windowPane)] variableName:nil -- 2.23.0