Commits

Zero King authored 4a4f7ab7a22
iTerm2: fix CVE-2019-9535

The Mozilla Foundation has generously sponsored a security audit of the iTerm2 source code. As part of this audit, a problem was discovered which could cause iTerm2 to issue commands in response to receiving certain input. This is a serious security issue because in some circumstances it could allow an attacker to execute commands on your machine when you view a file or otherwise receive input they have crafted in iTerm2.
No tags

aqua/iTerm2/files/patch-CVE-2019-9535.diff

Added
1 +From 538d570ea54614d3a2b5724f820953d717fbeb0c Mon Sep 17 00:00:00 2001
2 +From: George Nachman <gln@whatsapp.com>
3 +Date: Wed, 25 Sep 2019 23:13:00 -0700
4 +Subject: [PATCH] Do not send server-controlled values in tmux integration
5 + mode.
6 +
7 +CVE-2019-9535
8 +
9 +- Use session number everywhere rather than session name
10 +- Do not poll tmux for the set-titles-string, status-left, and status-right and
11 + then request the values of the returned format strings. Use ${T:} eval
12 + instead. These features are now only available for tmux 2.9 and later.
13 +- Hex-encode options saved in the tmux server to make them unexploitable (e.g.,
14 + hotkeys, window affinities, window origins, etc.). The old values are
15 + accepted as inputs but will never be produced as output.
16 +---
17 + iTerm2.xcodeproj/project.pbxproj | 8 +
18 + sources/PTYSession.m | 28 ++-
19 + sources/PTYTab.m | 13 +-
20 + sources/PseudoTerminal.m | 9 +-
21 + sources/TmuxController.h | 47 ++--
22 + sources/TmuxController.m | 305 +++++++++++++++-----------
23 + sources/TmuxDashboardController.m | 163 +++++++-------
24 + sources/TmuxSessionsTable.h | 30 +--
25 + sources/TmuxSessionsTable.m | 106 ++++-----
26 + sources/TmuxWindowsTable.h | 2 +-
27 + sources/TmuxWindowsTable.m | 21 +-
28 + sources/iTermInitialDirectory+Tmux.h | 2 +-
29 + sources/iTermInitialDirectory+Tmux.m | 20 +-
30 + sources/iTermTmuxOptionMonitor.h | 4 +
31 + sources/iTermTmuxOptionMonitor.m | 16 +-
32 + sources/iTermTmuxSessionObject.h | 17 ++
33 + sources/iTermTmuxSessionObject.m | 12 +
34 + sources/iTermTmuxStatusBarMonitor.h | 1 +
35 + sources/iTermTmuxStatusBarMonitor.m | 28 +--
36 + sources/iTermWorkingDirectoryPoller.m | 1 +
37 + 20 files changed, 471 insertions(+), 362 deletions(-)
38 + create mode 100644 sources/iTermTmuxSessionObject.h
39 + create mode 100644 sources/iTermTmuxSessionObject.m
40 +
41 +diff --git iTerm2.xcodeproj/project.pbxproj iTerm2.xcodeproj/project.pbxproj
42 +index 7e2f86a..32e1df2 100644
43 +--- iTerm2.xcodeproj/project.pbxproj
44 ++++ iTerm2.xcodeproj/project.pbxproj
45 +@@ -1376,6 +1376,8 @@
46 + 53E184F21FE32F2800DB78F3 /* iTermMetalBufferPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E184F01FE32F2800DB78F3 /* iTermMetalBufferPool.m */; };
47 + 53E8F36F2244A58800F3770F /* iTermActionsModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E8F36D2244A58800F3770F /* iTermActionsModel.h */; };
48 + 53E8F3702244A58800F3770F /* iTermActionsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E8F36E2244A58800F3770F /* iTermActionsModel.m */; };
49 ++ 53E98E7B233C6B760094D8A9 /* iTermTmuxSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */; };
50 ++ 53E98E7C233C6B760094D8A9 /* iTermTmuxSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */; };
51 + 53E9DFE0220D518E0070C9C0 /* AlertTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DCC15142D7FC10016228A /* AlertTrigger.m */; };
52 + 53E9DFE1220D51D50070C9C0 /* CoprocessTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DDD91142E5AC600275650 /* CoprocessTrigger.m */; };
53 + 53E9DFE2220D52730070C9C0 /* PasswordTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DABA03219253FEA00A228D8 /* PasswordTrigger.m */; };
54 +@@ -4429,6 +4431,8 @@
55 + 53E184F01FE32F2800DB78F3 /* iTermMetalBufferPool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermMetalBufferPool.m; sourceTree = "<group>"; };
56 + 53E8F36D2244A58800F3770F /* iTermActionsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermActionsModel.h; sourceTree = "<group>"; };
57 + 53E8F36E2244A58800F3770F /* iTermActionsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermActionsModel.m; sourceTree = "<group>"; };
58 ++ 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermTmuxSessionObject.h; sourceTree = "<group>"; };
59 ++ 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermTmuxSessionObject.m; sourceTree = "<group>"; };
60 + 53EBF29B1DCBFF7C00766613 /* iTermAPIServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermAPIServer.h; sourceTree = "<group>"; };
61 + 53EBF29C1DCBFF7C00766613 /* iTermAPIServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermAPIServer.m; sourceTree = "<group>"; };
62 + 53EBF29F1DCCF4AC00766613 /* iTermIPV4Address.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermIPV4Address.h; sourceTree = "<group>"; };
63 +@@ -7010,6 +7014,8 @@
64 + A6F718B4226438FC0053488E /* iTermInitialDirectory+Tmux.m */,
65 + 53D68F7E2283EE7C0018710D /* iTermTmuxLayoutBuilder.h */,
66 + 53D68F7F2283EE7C0018710D /* iTermTmuxLayoutBuilder.m */,
67 ++ 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */,
68 ++ 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */,
69 + );
70 + name = tmux;
71 + sourceTree = "<group>";
72 +@@ -9987,6 +9993,7 @@
73 + A6153D4C21F30A9C002976FC /* iTermJobTreeViewController.h in Headers */,
74 + 5370678F21C9D2780088D0F3 /* SIGSHA2VerificationAlgorithm.h in Headers */,
75 + A66719161DCE36C3000CE608 /* NSURL+iTerm.h in Headers */,
76 ++ 53E98E7B233C6B760094D8A9 /* iTermTmuxSessionObject.h in Headers */,
77 + A66719171DCE36C3000CE608 /* NSDate+iTerm.h in Headers */,
78 + A6BCAAD821F6F53E0000CD29 /* iTermTextPopoverViewController.h in Headers */,
79 + A66719181DCE36C3000CE608 /* iTermBaseHotKey.h in Headers */,
80 +@@ -12429,6 +12436,7 @@
81 + A6AC5D241E9036D70097C0A7 /* iTermURLMark.m in Sources */,
82 + 535EA51420D8C1EB00FC81E0 /* iTermProfilesWindowController.m in Sources */,
83 + A6A4866F20B67A1600493302 /* iTermSemanticHistoryPrefsController.m in Sources */,
84 ++ 53E98E7C233C6B760094D8A9 /* iTermTmuxSessionObject.m in Sources */,
85 + A66719621DCE3772000CE608 /* iTermSocketAddress.m in Sources */,
86 + A6A4867E20B67FD100493302 /* SmartSelectionController.m in Sources */,
87 + 53AFFC8E1DD2A04100E6CEC6 /* iTermLSOF.m in Sources */,
88 +diff --git sources/PTYSession.m sources/PTYSession.m
89 +index 9b64e03..0332d8c 100644
90 +--- sources/PTYSession.m
91 ++++ sources/PTYSession.m
92 +@@ -4726,7 +4726,7 @@ ITERM_WEAKLY_REFERENCEABLE
93 + } else {
94 + // Legacy code path for pre tmux 2.6
95 + [_tmuxController renameWindowWithId:_delegate.tmuxWindow
96 +- inSession:nil
97 ++ inSessionNumber:nil
98 + toName:profile[KEY_NAME]];
99 + }
100 + _tmuxTitleOutOfSync = NO;
101 +@@ -5910,14 +5910,18 @@ scrollToFirstResult:(BOOL)scrollToFirstResult {
102 +
103 + - (void)installTmuxStatusBarMonitor {
104 + assert(!_tmuxStatusBarMonitor);
105 +- _tmuxStatusBarMonitor = [[iTermTmuxStatusBarMonitor alloc] initWithGateway:_tmuxController.gateway
106 +- scope:self.variablesScope];
107 +- _tmuxStatusBarMonitor.active = [iTermProfilePreferences boolForKey:KEY_SHOW_STATUS_BAR inProfile:self.profile];
108 +- if ([iTermPreferences boolForKey:kPreferenceKeyUseTmuxStatusBar] ||
109 +- [iTermStatusBarLayout shouldOverrideLayout:self.profile[KEY_STATUS_BAR_LAYOUT]]) {
110 +- [self setSessionSpecificProfileValues:@{ KEY_STATUS_BAR_LAYOUT: [[iTermStatusBarLayout tmuxLayoutWithController:_tmuxController
111 +- scope:nil
112 +- window:self.delegate.tmuxWindow] dictionaryValue] }];
113 ++
114 ++ if (_tmuxController.gateway.minimumServerVersion.doubleValue >= 2.9) {
115 ++ // Just use the built-in status bar for older versions of tmux because they don't support ${T:xxx} or ${E:xxx}
116 ++ _tmuxStatusBarMonitor = [[iTermTmuxStatusBarMonitor alloc] initWithGateway:_tmuxController.gateway
117 ++ scope:self.variablesScope];
118 ++ _tmuxStatusBarMonitor.active = [iTermProfilePreferences boolForKey:KEY_SHOW_STATUS_BAR inProfile:self.profile];
119 ++ if ([iTermPreferences boolForKey:kPreferenceKeyUseTmuxStatusBar] ||
120 ++ [iTermStatusBarLayout shouldOverrideLayout:self.profile[KEY_STATUS_BAR_LAYOUT]]) {
121 ++ [self setSessionSpecificProfileValues:@{ KEY_STATUS_BAR_LAYOUT: [[iTermStatusBarLayout tmuxLayoutWithController:_tmuxController
122 ++ scope:nil
123 ++ window:self.delegate.tmuxWindow] dictionaryValue] }];
124 ++ }
125 + }
126 + }
127 +
128 +@@ -5929,7 +5933,8 @@ scrollToFirstResult:(BOOL)scrollToFirstResult {
129 + }
130 + __weak __typeof(self) weakSelf = self;
131 + _tmuxTitleMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:_tmuxController.gateway
132 +- scope:self.variablesScope
133 ++ scope:self.variablesScope
134 ++ fallbackVariableName:nil
135 + format:@"#{pane_title}"
136 + target:[NSString stringWithFormat:@"%%%@", @(self.tmuxPane)]
137 + variableName:iTermVariableKeySessionTmuxPaneTitle
138 +@@ -6308,8 +6313,7 @@ scrollToFirstResult:(BOOL)scrollToFirstResult {
139 + [_tmuxController ping];
140 + [_tmuxController validateOptions];
141 + [_tmuxController checkForUTF8];
142 +- [_tmuxController guessVersion];
143 +- [_tmuxController loadTitleFormat];
144 ++ [_tmuxController guessVersion]; // NOTE: This kicks off more stuff that depends on knowing the version number.
145 + }
146 +
147 + - (void)tmuxInitialCommandDidFailWithError:(NSString *)error {
148 +diff --git sources/PTYTab.m sources/PTYTab.m
149 +index a8f8b48..6c4766e 100644
150 +--- sources/PTYTab.m
151 ++++ sources/PTYTab.m
152 +@@ -3690,11 +3690,12 @@ static void SetAgainstGrainDim(BOOL isVertical, NSSize *dest, CGFloat value) {
153 + return;
154 + }
155 + _tmuxTitleMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:tmuxController_.gateway
156 +- scope:self.variablesScope
157 +- format:tmuxController_.setTitlesString
158 +- target:[NSString stringWithFormat:@"@%@", @(self.tmuxWindow)]
159 +- variableName:iTermVariableKeyTabTmuxWindowTitle
160 +- block:nil];
161 ++ scope:self.variablesScope
162 ++ fallbackVariableName:iTermVariableKeySessionWindowName
163 ++ format:@"#{T:set-titles-string}"
164 ++ target:[NSString stringWithFormat:@"@%@", @(self.tmuxWindow)]
165 ++ variableName:iTermVariableKeyTabTmuxWindowTitle
166 ++ block:nil];
167 + [_tmuxTitleMonitor updateOnce];
168 + if (self.titleOverride.length == 0) {
169 + // Show the tmux window title if both the tmux option set-titles is on and the user hasn't
170 +@@ -4312,7 +4313,7 @@ static void SetAgainstGrainDim(BOOL isVertical, NSSize *dest, CGFloat value) {
171 + if (self.tmuxTab) {
172 + if (titleOverride) {
173 + [self.tmuxController renameWindowWithId:self.tmuxWindow
174 +- inSession:nil
175 ++ inSessionNumber:nil
176 + toName:titleOverride];
177 + }
178 + return;
179 +diff --git sources/PseudoTerminal.m sources/PseudoTerminal.m
180 +index b90e733..24f041a 100644
181 +--- sources/PseudoTerminal.m
182 ++++ sources/PseudoTerminal.m
183 +@@ -3018,8 +3018,8 @@ ITERM_WEAKLY_REFERENCEABLE
184 + if ([arrangement objectForKey:TERMINAL_GUID] &&
185 + [[arrangement objectForKey:TERMINAL_GUID] isKindOfClass:[NSString class]]) {
186 + NSString *savedGUID = [arrangement objectForKey:TERMINAL_GUID];
187 +- if ([[iTermController sharedInstance] terminalWithGuid:savedGUID]) {
188 +- // Refuse to create a window with an already-used guid.
189 ++ if ([[iTermController sharedInstance] terminalWithGuid:savedGUID] || ![self stringIsValidTerminalGuid:savedGUID]) {
190 ++ // Refuse to create a window with an already-used or invalid guid.
191 + self.terminalGuid = [NSString stringWithFormat:@"pty-%@", [NSString uuid]];
192 + } else {
193 + self.terminalGuid = savedGUID;
194 +@@ -3039,6 +3039,11 @@ ITERM_WEAKLY_REFERENCEABLE
195 + return YES;
196 + }
197 +
198 ++- (BOOL)stringIsValidTerminalGuid:(NSString *)string {
199 ++ NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"];
200 ++ return [string rangeOfCharacterFromSet:characterSet.invertedSet].location == NSNotFound;
201 ++}
202 ++
203 + - (BOOL)restoreTabsFromArrangement:(NSDictionary *)arrangement sessions:(NSArray<PTYSession *> *)sessions {
204 + for (NSDictionary *tabArrangement in arrangement[TERMINAL_ARRANGEMENT_TABS]) {
205 + NSDictionary<NSString *, PTYSession *> *sessionMap = nil;
206 +diff --git sources/TmuxController.h sources/TmuxController.h
207 +index f27bacc..f1e5ad3 100644
208 +--- sources/TmuxController.h
209 ++++ sources/TmuxController.h
210 +@@ -8,6 +8,7 @@
211 + #import <Cocoa/Cocoa.h>
212 + #import "ProfileModel.h"
213 + #import "iTermInitialDirectory.h"
214 ++#import "iTermTmuxSessionObject.h"
215 + #import "TmuxGateway.h"
216 + #import "WindowControllerInterface.h"
217 +
218 +@@ -33,7 +34,7 @@ extern NSString *const kTmuxControllerWindowDidClose;
219 + extern NSString *const kTmuxControllerAttachedSessionDidChange;
220 + // Posted when a session changes name
221 + extern NSString *const kTmuxControllerSessionWasRenamed;
222 +-// Posted when set-titles-string option changes. Object is tmux controller.
223 ++// Posted when set-titles option changes. Object is tmux controller.
224 + extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption;
225 +
226 + @interface TmuxController : NSObject
227 +@@ -41,7 +42,7 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption;
228 + @property(nonatomic, readonly) TmuxGateway *gateway;
229 + @property(nonatomic, retain) NSMutableDictionary *windowPositions;
230 + @property(nonatomic, copy) NSString *sessionName;
231 +-@property(nonatomic, retain) NSArray *sessions;
232 ++@property(nonatomic, copy) NSArray<iTermTmuxSessionObject *> *sessionObjects;
233 + @property(nonatomic, assign) BOOL ambiguousIsDoubleWidth;
234 + @property(nonatomic, assign) NSInteger unicodeVersion;
235 + @property(nonatomic, readonly) NSString *clientName;
236 +@@ -53,7 +54,6 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption;
237 + @property(nonatomic, readonly) NSDictionary *sharedFontOverrides;
238 + @property(nonatomic, readonly) NSString *sessionGuid;
239 + @property(nonatomic, readonly) BOOL variableWindowSize;
240 +-@property(nonatomic, readonly) NSString *setTitlesString;
241 + @property(nonatomic, readonly) BOOL shouldSetTitles;
242 + @property(nonatomic, readonly) BOOL serverIsLocal;
243 +
244 +@@ -120,9 +120,9 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption;
245 + scope:(iTermVariableScope *)scope
246 + initialDirectory:(iTermInitialDirectory *)initialDirectory;
247 +
248 +-- (void)newWindowInSession:(NSString *)targetSession
249 +- scope:(iTermVariableScope *)scope
250 +- initialDirectory:(iTermInitialDirectory *)initialDirectory;
251 ++- (void)newWindowInSessionNumber:(NSNumber *)sessionNumber
252 ++ scope:(iTermVariableScope *)scope
253 ++ initialDirectory:(iTermInitialDirectory *)initialDirectory;
254 +
255 + - (void)selectPane:(int)windowPane;
256 +
257 +@@ -150,9 +150,11 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption;
258 +
259 + - (void)killWindowPane:(int)windowPane;
260 + - (void)killWindow:(int)window;
261 +-- (void)unlinkWindowWithId:(int)windowId inSession:(NSString *)sessionName;
262 ++- (void)unlinkWindowWithId:(int)windowId;
263 + - (void)requestDetach;
264 +-- (void)renameWindowWithId:(int)windowId inSession:(NSString *)sessionName toName:(NSString *)newName;
265 ++- (void)renameWindowWithId:(int)windowId
266 ++ inSessionNumber:(NSNumber *)sessionNumber
267 ++ toName:(NSString *)newName;
268 + - (BOOL)canRenamePane;
269 + - (void)renamePane:(int)windowPane toTitle:(NSString *)newTitle;
270 + - (void)setHotkeyForWindowPane:(int)windowPane to:(NSDictionary *)hotkey;
271 +@@ -162,21 +164,25 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption;
272 + - (NSString *)tabColorStringForWindowPane:(int)windowPane;
273 +
274 + - (void)linkWindowId:(int)windowId
275 +- inSession:(NSString *)sessionName
276 +- toSession:(NSString *)targetSession;
277 ++ inSessionNumber:(int)sessionNumber
278 ++ toSessionNumber:(int)targetSession;
279 ++
280 + - (void)moveWindowId:(int)windowId
281 +- inSession:(NSString *)sessionName
282 +- toSession:(NSString *)targetSession;
283 ++ inSessionNumber:(int)sessionNumber
284 ++ toSessionNumber:(int)targetSessionNumber;
285 ++
286 ++- (void)renameSessionNumber:(int)sessionNumber
287 ++ to:(NSString *)newName;
288 +
289 +-- (void)renameSession:(NSString *)oldName to:(NSString *)newName;
290 +-- (void)killSession:(NSString *)sessionName;
291 +-- (void)attachToSession:(NSString *)sessionName;
292 ++- (void)killSessionNumber:(int)sessionNumber;
293 ++- (void)attachToSessionWithNumber:(int)sessionNumber;
294 + - (void)addSessionWithName:(NSString *)sessionName;
295 +-// NOTE: If the session name is bogus (or any other error occurs) the selector will not be called.
296 +-- (void)listWindowsInSession:(NSString *)sessionName
297 +- target:(id)target
298 +- selector:(SEL)selector
299 +- object:(id)object;
300 ++// NOTE: If anything goes wrong the selector will not be called.
301 ++- (void)listWindowsInSessionNumber:(int)sessionNumber
302 ++ target:(id)target
303 ++ selector:(SEL)selector
304 ++ object:(id)object;
305 ++
306 + - (void)listSessions;
307 + - (void)saveAffinities;
308 + - (void)saveWindowOrigins;
309 +@@ -200,7 +206,6 @@ extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption;
310 + - (void)setLayoutInWindow:(int)window toLayout:(NSString *)layout;
311 + - (NSArray<PTYSession *> *)clientSessions;
312 +
313 +-- (void)setSize:(NSSize)size windows:(NSArray<NSString *> *)windows;
314 + - (void)setSize:(NSSize)size window:(int)window;
315 +
316 + @end
317 +diff --git sources/TmuxController.m sources/TmuxController.m
318 +index 08cbd91..77b0424 100644
319 +--- sources/TmuxController.m
320 ++++ sources/TmuxController.m
321 +@@ -20,6 +20,7 @@
322 + #import "iTermShortcut.h"
323 + #import "iTermTuple.h"
324 + #import "NSArray+iTerm.h"
325 ++#import "NSData+iTerm.h"
326 + #import "NSFont+iTerm.h"
327 + #import "NSStringITerm.h"
328 + #import "PreferencePanel.h"
329 +@@ -42,6 +43,12 @@ NSString *const kTmuxControllerWindowDidClose = @"kTmuxControllerWindowDidClose"
330 + NSString *const kTmuxControllerSessionWasRenamed = @"kTmuxControllerSessionWasRenamed";
331 + NSString *const kTmuxControllerDidFetchSetTitlesStringOption = @"kTmuxControllerDidFetchSetTitlesStringOption";
332 +
333 ++static NSString *const iTermTmuxControllerEncodingPrefixHotkeys = @"h_";
334 ++static NSString *const iTermTmuxControllerEncodingPrefixTabColors = @"t_";
335 ++static NSString *const iTermTmuxControllerEncodingPrefixAffinities = @"a_";
336 ++static NSString *const iTermTmuxControllerEncodingPrefixOrigins = @"o_";
337 ++static NSString *const iTermTmuxControllerEncodingPrefixHidden = @"i_";
338 ++
339 + // Unsupported global options:
340 + static NSString *const kAggressiveResize = @"aggressive-resize";
341 +
342 +@@ -81,7 +88,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
343 + TmuxGateway *gateway_;
344 + NSMutableDictionary *windowPanes_; // paneId -> PTYSession *
345 + NSMutableDictionary<NSNumber *, iTermTmuxWindowState *> *_windowStates; // Key is window number
346 +- NSArray *sessions_;
347 ++ NSArray<iTermTmuxSessionObject *> *sessionObjects_;
348 + int numOutstandingWindowResizes_;
349 + NSMutableDictionary *windowPositions_;
350 + NSSize lastSize_; // last size for windowDidChange:
351 +@@ -97,7 +104,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
352 + BOOL windowOriginsDirty_;
353 + BOOL haveOutstandingSaveWindowOrigins_;
354 + NSMutableDictionary *origins_; // window id -> NSValue(Point) window origin
355 +- NSMutableSet *hiddenWindows_;
356 ++ NSMutableSet<NSNumber *> *hiddenWindows_;
357 + NSTimer *listSessionsTimer_; // Used to do a cancelable delayed perform of listSessions.
358 + NSTimer *listWindowsTimer_; // Used to do a cancelable delayed perform of listWindows.
359 + BOOL ambiguousIsDoubleWidth_;
360 +@@ -119,7 +126,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
361 + @synthesize gateway = gateway_;
362 + @synthesize windowPositions = windowPositions_;
363 + @synthesize sessionName = sessionName_;
364 +-@synthesize sessions = sessions_;
365 ++@synthesize sessionObjects = sessionObjects_;
366 + @synthesize ambiguousIsDoubleWidth = ambiguousIsDoubleWidth_;
367 + @synthesize sessionId = sessionId_;
368 +
369 +@@ -180,8 +187,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
370 + [_sharedFontOverrides release];
371 + [_pendingWindows release];
372 + [sessionName_ release];
373 +- [sessions_ release];
374 +- [_setTitlesString release];
375 ++ [sessionObjects_ release];
376 +
377 + [super dealloc];
378 + }
379 +@@ -392,16 +398,22 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
380 + }
381 + [windowsToOpen addObject:record];
382 + }
383 ++ BOOL tooMany = NO;
384 + if (windowsToOpen.count > [iTermPreferences intForKey:kPreferenceKeyTmuxDashboardLimit]) {
385 + DLog(@"There are too many windows to open so just show the dashboard");
386 + haveHidden = YES;
387 ++ tooMany = YES;
388 + [windowsToOpen removeAllObjects];
389 + }
390 + if (haveHidden) {
391 + DLog(@"Hidden windows existing, showing dashboard");
392 + [[TmuxDashboardController sharedInstance] showWindow:nil];
393 + [[[TmuxDashboardController sharedInstance] window] makeKeyAndOrderFront:nil];
394 +- [[iTermNotificationController sharedInstance] notify:@"Too many tmux windows!" withDescription:@"Use the tmux dashboard to select which to open."];
395 ++ if (tooMany) {
396 ++ [[iTermNotificationController sharedInstance] notify:@"Too many tmux windows!" withDescription:@"Use the tmux dashboard to select which to open."];
397 ++ } else {
398 ++ [[iTermNotificationController sharedInstance] notify:@"Some tmux windows were hidden." withDescription:@"Use the tmux dashboard to select which to open."];
399 ++ }
400 + }
401 + for (NSArray *record in windowsToOpen) {
402 + DLog(@"Open window %@", record);
403 +@@ -459,7 +471,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
404 + NSString *setSizeCommand = [NSString stringWithFormat:@"refresh-client -C %d,%d",
405 + size.width, [self adjustHeightForStatusBar:size.height]];
406 + NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@", kListWindowsFormat];
407 +- NSString *listSessionsCommand = @"list-sessions -F \"#{session_name}\"";
408 ++ NSString *listSessionsCommand = @"list-sessions -F \"#{session_id} #{session_name}\"";
409 + NSString *getAffinitiesCommand = [NSString stringWithFormat:@"show -v -q -t $%d @affinities", sessionId_];
410 + NSString *getOriginsCommand = [NSString stringWithFormat:@"show -v -q -t $%d @origins", sessionId_];
411 + NSString *getHotkeysCommand = [NSString stringWithFormat:@"show -v -q -t $%d @hotkeys", sessionId_];
412 +@@ -715,10 +727,6 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
413 + }
414 + }
415 +
416 +-- (void)setSize:(NSSize)size windows:(NSArray<NSString *> *)windows {
417 +- [gateway_ sendCommandList:[self commandListToSetSize:size ofWindows:windows]];
418 +-}
419 +-
420 + - (void)setWindowSizes:(NSArray<iTermTuple<NSString *, NSValue *> *> *)windowSizes {
421 + [gateway_ sendCommandList:[self commandListToSetWindowSizes:windowSizes]];
422 + }
423 +@@ -727,14 +735,14 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
424 + return [windowSizes mapWithBlock:^NSDictionary *(iTermTuple<NSString *,NSValue *> *tuple) {
425 + NSString *window = tuple.firstObject;
426 + NSSize size = tuple.secondObject.sizeValue;
427 +- if ([window hasPrefix:@"pty"]) {
428 ++ if ([window hasPrefix:@"pty"] || [window hasSuffix:@"_ph"]) {
429 + return nil;
430 + }
431 + // 10000 comes from WINDOW_MAXIMUM in tmux.h
432 + if (size.width < 1 || size.height < 1 || size.width >= 10000 || size.height >= 10000) {
433 + return nil;
434 + }
435 +- NSString *command = [NSString stringWithFormat:@"resize-window -x %@ -y %@ -t @%@", @((int)size.width), @((int)size.height), window];
436 ++ NSString *command = [NSString stringWithFormat:@"resize-window -x %@ -y %@ -t @%d", @((int)size.width), @((int)size.height), window.intValue];
437 + NSDictionary *dict = [gateway_ dictionaryForCommand:command
438 + responseTarget:self
439 + responseSelector:@selector(handleResizeWindowResponse:)
440 +@@ -857,11 +865,6 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
441 + [gateway_ sendCommandList:@[ [gateway_ dictionaryForCommand:@"show-options -v -g set-titles"
442 + responseTarget:self
443 + responseSelector:@selector(handleShowSetTitles:)
444 +- responseObject:nil
445 +- flags:0],
446 +- [gateway_ dictionaryForCommand:@"show-options -v -g set-titles-string"
447 +- responseTarget:self
448 +- responseSelector:@selector(handleShowSetTitlesString:)
449 + responseObject:nil
450 + flags:0] ]];
451 + }
452 +@@ -872,10 +875,6 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
453 + object:self];
454 + }
455 +
456 +-- (void)handleShowSetTitlesString:(NSString *)setTitlesString {
457 +- _setTitlesString = [setTitlesString copy];
458 +-}
459 +-
460 + - (void)guessVersion {
461 + // Run commands that will fail in successively older versions.
462 + // show-window-options pane-border-format will succeed in 2.3 and later (presumably. 2.3 isn't out yet)
463 +@@ -1013,17 +1012,25 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
464 + }
465 +
466 + // This is the oldest version supported. By the time you get here you know the version.
467 ++ [self didGuessVersion];
468 ++}
469 ++
470 ++// Actions to perform after the version number is known.
471 ++- (void)didGuessVersion {
472 + [self loadServerPID];
473 ++ [self loadTitleFormat];
474 + }
475 +
476 +-- (BOOL)recyclingSupported {
477 +- NSDecimalNumber *version1_9 = [NSDecimalNumber decimalNumberWithString:@"1.9"];
478 +- if (gateway_.minimumServerVersion != nil) {
479 +- return ([gateway_.minimumServerVersion compare:version1_9] != NSOrderedAscending);
480 +- } else {
481 +- // Assume 1.8
482 ++- (BOOL)versionAtLeastDecimalNumberWithString:(NSString *)string {
483 ++ NSDecimalNumber *version = [NSDecimalNumber decimalNumberWithString:string];
484 ++ if (gateway_.minimumServerVersion == nil) {
485 + return NO;
486 + }
487 ++ return ([gateway_.minimumServerVersion compare:version] != NSOrderedAscending);
488 ++}
489 ++
490 ++- (BOOL)recyclingSupported {
491 ++ return [self versionAtLeastDecimalNumberWithString:@"1.9"];
492 + }
493 +
494 + // Show an error and terminate the connection because tmux has an unsupported option turned on.
495 +@@ -1143,10 +1150,10 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
496 + responseSelector:nil];
497 + }
498 +
499 +-- (void)newWindowInSession:(NSString *)targetSession
500 +- scope:(iTermVariableScope *)scope
501 +- initialDirectory:(iTermInitialDirectory *)initialDirectory {
502 +- [initialDirectory tmuxNewWindowCommandInSession:targetSession
503 ++- (void)newWindowInSessionNumber:(NSNumber *)sessionNumber
504 ++ scope:(iTermVariableScope *)scope
505 ++ initialDirectory:(iTermInitialDirectory *)initialDirectory {
506 ++ [initialDirectory tmuxNewWindowCommandInSessionNumber:sessionNumber
507 + recyclingSupported:self.recyclingSupported
508 + scope:scope
509 + completion:
510 +@@ -1164,7 +1171,11 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
511 + responseObject:nil
512 + flags:0]];
513 + }
514 +- [commands addObject:[gateway_ dictionaryForCommand:command responseTarget:nil responseSelector:nil responseObject:nil flags:0]];
515 ++ [commands addObject:[gateway_ dictionaryForCommand:command
516 ++ responseTarget:nil
517 ++ responseSelector:nil
518 ++ responseObject:nil
519 ++ flags:0]];
520 + [gateway_ sendCommandList:commands];
521 + }];
522 + }
523 +@@ -1218,8 +1229,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
524 + responseSelector:nil];
525 + }
526 +
527 +-- (void)unlinkWindowWithId:(int)windowId inSession:(NSString *)sessionName
528 +-{
529 ++- (void)unlinkWindowWithId:(int)windowId {
530 + [gateway_ sendCommand:[NSString stringWithFormat:@"unlink-window -k -t @%d", windowId]
531 + responseTarget:nil
532 + responseSelector:nil
533 +@@ -1232,11 +1242,14 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
534 + }
535 +
536 + - (void)renameWindowWithId:(int)windowId
537 +- inSession:(NSString *)sessionName
538 ++ inSessionNumber:(NSNumber *)sessionNumber
539 + toName:(NSString *)newName {
540 + NSString *theCommand;
541 +- if (sessionName) {
542 +- theCommand = [NSString stringWithFormat:@"rename-window -t \"%@:@%d\" \"%@\"", sessionName, windowId, [self stringByEscapingBackslashesAndRemovingNewlines:newName]];
543 ++ if (sessionNumber) {
544 ++ theCommand = [NSString stringWithFormat:@"rename-window -t \"$%d:@%d\" \"%@\"",
545 ++ sessionNumber.intValue,
546 ++ windowId,
547 ++ [self stringByEscapingBackslashesAndRemovingNewlines:newName]];
548 + } else {
549 + theCommand = [NSString stringWithFormat:@"rename-window -t @%d \"%@\"", windowId, [self stringByEscapingBackslashesAndRemovingNewlines:newName]];
550 + }
551 +@@ -1312,9 +1325,23 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
552 + [self sendCommandToSetTabColors];
553 + }
554 +
555 ++- (NSString *)encodedString:(NSString *)string prefix:(NSString *)prefix {
556 ++ return [prefix stringByAppendingString:[[string dataUsingEncoding:NSUTF8StringEncoding] it_hexEncoded]];
557 ++}
558 ++
559 ++- (NSString *)decodedString:(NSString *)string optionalPrefix:(NSString *)prefix {
560 ++ if (![string hasPrefix:prefix]) {
561 ++ return string;
562 ++ }
563 ++ return [[[NSString alloc] initWithData:[[string substringFromIndex:prefix.length] dataFromHexValues]
564 ++ encoding:NSUTF8StringEncoding] autorelease];
565 ++}
566 ++
567 + - (void)sendCommandToSetHotkeys {
568 ++ NSString *hexEncoded = [self encodedString:[self.hotkeysString stringByEscapingQuotes]
569 ++ prefix:iTermTmuxControllerEncodingPrefixHotkeys];
570 + NSString *command = [NSString stringWithFormat:@"set -t $%d @hotkeys \"%@\"",
571 +- sessionId_, [self.hotkeysString stringByEscapingQuotes]];
572 ++ sessionId_, hexEncoded];
573 + [gateway_ sendCommand:command
574 + responseTarget:nil
575 + responseSelector:nil
576 +@@ -1323,8 +1350,10 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
577 + }
578 +
579 + - (void)sendCommandToSetTabColors {
580 ++
581 + NSString *command = [NSString stringWithFormat:@"set -t $%d @tab_colors \"%@\"",
582 +- sessionId_, [self.tabColorsString stringByEscapingQuotes]];
583 ++ sessionId_, [self encodedString:[self.tabColorsString stringByEscapingQuotes]
584 ++ prefix:iTermTmuxControllerEncodingPrefixTabColors]];
585 + [gateway_ sendCommand:command
586 + responseTarget:nil
587 + responseSelector:nil
588 +@@ -1376,7 +1405,8 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
589 +
590 + - (void)breakOutWindowPane:(int)windowPane toTabAside:(NSString *)sibling
591 + {
592 +- [gateway_ sendCommand:[NSString stringWithFormat:@"break-pane -P -F \"#{window_id}\" %@ \"%%%d\"", [self breakPaneWindowPaneFlag], windowPane]
593 ++ [gateway_ sendCommand:[NSString stringWithFormat:@"break-pane -P -F \"#{window_id}\" %@ \"%%%d\"",
594 ++ [self breakPaneWindowPaneFlag], windowPane]
595 + responseTarget:self
596 + responseSelector:@selector(windowPaneBrokeOutWithWindowId:setAffinityTo:)
597 + responseObject:sibling
598 +@@ -1435,19 +1465,19 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
599 + }
600 +
601 + - (void)linkWindowId:(int)windowId
602 +- inSession:(NSString *)sessionName
603 +- toSession:(NSString *)targetSession {
604 +- [gateway_ sendCommand:[NSString stringWithFormat:@"link-window -s \"%@:@%d\" -t \"%@:+\"",
605 +- sessionName, windowId, targetSession]
606 ++ inSessionNumber:(int)sessionNumber
607 ++ toSessionNumber:(int)targetSessionNumber {
608 ++ [gateway_ sendCommand:[NSString stringWithFormat:@"link-window -s \"$%d:@%d\" -t \"$%d:+\"",
609 ++ sessionNumber, windowId, targetSessionNumber]
610 + responseTarget:nil
611 + responseSelector:nil];
612 + }
613 +
614 + - (void)moveWindowId:(int)windowId
615 +- inSession:(NSString *)sessionName
616 +- toSession:(NSString *)targetSession {
617 +- [gateway_ sendCommand:[NSString stringWithFormat:@"move-window -s \"%@:@%d\" -t \"%@:+\"",
618 +- sessionName, windowId, targetSession]
619 ++ inSessionNumber:(int)sessionNumber
620 ++ toSessionNumber:(int)targetSessionNumber {
621 ++ [gateway_ sendCommand:[NSString stringWithFormat:@"move-window -s \"$%d:@%d\" -t \"$%d:+\"",
622 ++ sessionNumber, windowId, targetSessionNumber]
623 + responseTarget:nil
624 + responseSelector:nil];
625 + }
626 +@@ -1467,19 +1497,19 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
627 + return pos;
628 + }
629 +
630 +-- (void)renameSession:(NSString *)oldName to:(NSString *)newName
631 +-{
632 +- NSString *renameCommand = [NSString stringWithFormat:@"rename-session -t \"%@\" \"%@\"",
633 +- [oldName stringByEscapingQuotes],
634 ++- (void)renameSessionNumber:(int)sessionNumber
635 ++ to:(NSString *)newName {
636 ++ NSString *renameCommand = [NSString stringWithFormat:@"rename-session -t \"$%d\" \"%@\"",
637 ++ sessionNumber,
638 + [newName stringByEscapingQuotes]];
639 + [gateway_ sendCommand:renameCommand responseTarget:nil responseSelector:nil];
640 + }
641 +
642 +-- (void)killSession:(NSString *)sessionName
643 +-{
644 +- NSString *killCommand = [NSString stringWithFormat:@"kill-session -t \"%@\"",
645 +- [sessionName stringByEscapingQuotes]];
646 +- [gateway_ sendCommand:killCommand responseTarget:nil responseSelector:nil];
647 ++- (void)killSessionNumber:(int)sessionNumber {
648 ++ NSString *killCommand = [NSString stringWithFormat:@"kill-session -t \"$%d\"", sessionNumber];
649 ++ [gateway_ sendCommand:killCommand
650 ++ responseTarget:nil
651 ++ responseSelector:nil];
652 + [self listSessions];
653 + }
654 +
655 +@@ -1493,44 +1523,46 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
656 + [self listSessions];
657 + }
658 +
659 +-- (void)attachToSession:(NSString *)sessionName
660 +-{
661 +- NSString *attachCommand = [NSString stringWithFormat:@"attach-session -t \"%@\"",
662 +- [sessionName stringByEscapingQuotes]];
663 ++- (void)attachToSessionWithNumber:(int)sessionNumber {
664 ++ NSString *attachCommand = [NSString stringWithFormat:@"attach-session -t \"$%d\"", sessionNumber];
665 + [gateway_ sendCommand:attachCommand
666 + responseTarget:nil
667 + responseSelector:nil];
668 + }
669 +
670 +-- (void)listWindowsInSession:(NSString *)sessionName
671 +- target:(id)target
672 +- selector:(SEL)selector
673 +- object:(id)object {
674 ++- (void)listWindowsInSessionNumber:(int)sessionNumber
675 ++ target:(id)target
676 ++ selector:(SEL)selector
677 ++ object:(id)object {
678 + if (detached_ || !object) {
679 + // This can happen if you're not attached to a session.
680 + return;
681 + }
682 +- NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@ -t \"%@\"",
683 +- kListWindowsFormat, sessionName];
684 ++ NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@ -t \"$%d\"",
685 ++ kListWindowsFormat, sessionNumber];
686 + // Wait a few seconds. We always get a windows-close notification when the last window in
687 + // a window closes. To avoid spamming the command line with list-windows, we wait a bit to see
688 + // if there is an exit notification coming down the pipe.
689 + const CGFloat kListWindowsDelay = 1.5;
690 + [listWindowsTimer_ invalidate];
691 +- listWindowsTimer_ = [NSTimer scheduledTimerWithTimeInterval:kListWindowsDelay
692 +- target:self
693 +- selector:@selector(listWindowsTimerFired:)
694 +- userInfo:[NSArray arrayWithObjects:listWindowsCommand, object, target, NSStringFromSelector(selector), nil]
695 +- repeats:NO];
696 ++ listWindowsTimer_ =
697 ++ [NSTimer scheduledTimerWithTimeInterval:kListWindowsDelay
698 ++ target:self
699 ++ selector:@selector(listWindowsTimerFired:)
700 ++ userInfo:@[listWindowsCommand,
701 ++ object,
702 ++ target,
703 ++ NSStringFromSelector(selector) ]
704 ++ repeats:NO];
705 + }
706 +
707 + - (void)listWindowsTimerFired:(NSTimer *)timer
708 + {
709 + NSArray *array = [timer userInfo];
710 +- NSString *command = [array objectAtIndex:0];
711 +- id object = [array objectAtIndex:1];
712 +- id target = [array objectAtIndex:2];
713 +- NSString *selector = [array objectAtIndex:3];
714 ++ NSString *command = array[0];
715 ++ id object = array[1];
716 ++ id target = array[2];
717 ++ NSString *selector = array[3];
718 +
719 + [listWindowsTimer_ invalidate];
720 + listWindowsTimer_ = nil;
721 +@@ -1538,10 +1570,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
722 + [gateway_ sendCommand:command
723 + responseTarget:self
724 + responseSelector:@selector(didListWindows:userData:)
725 +- responseObject:[NSArray arrayWithObjects:object,
726 +- selector,
727 +- target,
728 +- nil]
729 ++ responseObject:@[object, selector, target]
730 + flags:kTmuxGatewayCommandShouldTolerateErrors]; // Tolerates errors because the session may have been detached by the time we get the notification or the timer fires.
731 + }
732 +
733 +@@ -1549,8 +1578,10 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
734 + {
735 + NSString *hidden = [[hiddenWindows_ allObjects] componentsJoinedByString:@","];
736 + NSString *command = [NSString stringWithFormat:
737 +- @"set -t $%d @hidden \"%@\"",
738 +- sessionId_, hidden];
739 ++ @"set -t $%d @hidden \"%@\"",
740 ++ sessionId_,
741 ++ [self encodedString:hidden
742 ++ prefix:iTermTmuxControllerEncodingPrefixHidden]];
743 + [gateway_ sendCommand:command
744 + responseTarget:nil
745 + responseSelector:nil
746 +@@ -1594,7 +1625,9 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
747 + NSString *enc = [maps componentsJoinedByString:@" "];
748 + DLog(@"Save window origins to %@ called from %@", enc, [NSThread callStackSymbols]);
749 + NSString *command = [NSString stringWithFormat:@"set -t $%d @origins \"%@\"",
750 +- sessionId_, [enc stringByEscapingQuotes]];
751 ++ sessionId_,
752 ++ [self encodedString:[enc stringByEscapingQuotes]
753 ++ prefix:iTermTmuxControllerEncodingPrefixOrigins]];
754 + if (!lastOrigins_ || ![command isEqualToString:lastOrigins_]) {
755 + [lastOrigins_ release];
756 + lastOrigins_ = [command copy];
757 +@@ -1603,7 +1636,8 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
758 + responseTarget:self
759 + responseSelector:@selector(saveWindowOriginsResponse:)];
760 + }
761 +- [self getOriginsResponse:enc];
762 ++ [self getOriginsResponse:[self encodedString:[enc stringByEscapingQuotes]
763 ++ prefix:iTermTmuxControllerEncodingPrefixOrigins]];
764 + }
765 +
766 + - (void)saveWindowOriginsResponse:(NSString *)response
767 +@@ -1651,7 +1685,8 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
768 + // Update affinities if any have changed.
769 + NSString *arg = [affinities componentsJoinedByString:@" "];
770 + NSString *command = [NSString stringWithFormat:@"set -t $%d @affinities \"%@\"",
771 +- sessionId_, [arg stringByEscapingQuotes]];
772 ++ sessionId_, [self encodedString:[arg stringByEscapingQuotes]
773 ++ prefix:iTermTmuxControllerEncodingPrefixAffinities]];
774 + if ([command isEqualToString:lastSaveAffinityCommand_]) {
775 + return;
776 + }
777 +@@ -1763,7 +1798,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
778 + responseSelector:@selector(didSetLayout:)
779 + responseObject:nil
780 + flags:0],
781 +- [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"%@\"", sessionName_]
782 ++ [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"$%d\"", sessionId_]
783 + responseTarget:self
784 + responseSelector:@selector(didListWindowsSubsequentToSettingLayout:)
785 + responseObject:nil
786 +@@ -1777,7 +1812,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
787 + responseSelector:@selector(didSetLayout:)
788 + responseObject:nil
789 + flags:0],
790 +- [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"%@\"", sessionName_]
791 ++ [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"$%d\"", sessionId_]
792 + responseTarget:self
793 + responseSelector:@selector(didListWindowsSubsequentToSettingLayout:)
794 + responseObject:nil
795 +@@ -1798,31 +1833,32 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
796 +
797 + #pragma mark - Private
798 +
799 +-- (void)getOriginsResponse:(NSString *)result
800 +-{
801 +- [origins_ removeAllObjects];
802 +- if ([result length] > 0) {
803 +- NSArray *windows = [result componentsSeparatedByString:@" "];
804 +- for (NSString *wstr in windows) {
805 +- NSArray *tuple = [wstr componentsSeparatedByString:@":"];
806 +- if (tuple.count != 2) {
807 +- continue;
808 +- }
809 +- NSString *windowsStr = [tuple objectAtIndex:0];
810 +- NSString *coords = [tuple objectAtIndex:1];
811 +- NSArray *windowIds = [windowsStr componentsSeparatedByString:@","];
812 +- NSArray *xy = [coords componentsSeparatedByString:@","];
813 +- if (xy.count != 2) {
814 +- continue;
815 +- }
816 +- NSPoint origin = NSMakePoint([[xy objectAtIndex:0] intValue],
817 +- [[xy objectAtIndex:1] intValue]);
818 +- for (NSString *wid in windowIds) {
819 +- [origins_ setObject:[NSValue valueWithPoint:origin]
820 +- forKey:[NSNumber numberWithInt:[wid intValue]]];
821 +- }
822 +- }
823 +- }
824 ++- (void)getOriginsResponse:(NSString *)encodedResult {
825 ++ NSString *result = [self decodedString:encodedResult
826 ++ optionalPrefix:iTermTmuxControllerEncodingPrefixOrigins];
827 ++ [origins_ removeAllObjects];
828 ++ if ([result length] > 0) {
829 ++ NSArray *windows = [result componentsSeparatedByString:@" "];
830 ++ for (NSString *wstr in windows) {
831 ++ NSArray *tuple = [wstr componentsSeparatedByString:@":"];
832 ++ if (tuple.count != 2) {
833 ++ continue;
834 ++ }
835 ++ NSString *windowsStr = [tuple objectAtIndex:0];
836 ++ NSString *coords = [tuple objectAtIndex:1];
837 ++ NSArray *windowIds = [windowsStr componentsSeparatedByString:@","];
838 ++ NSArray *xy = [coords componentsSeparatedByString:@","];
839 ++ if (xy.count != 2) {
840 ++ continue;
841 ++ }
842 ++ NSPoint origin = NSMakePoint([[xy objectAtIndex:0] intValue],
843 ++ [[xy objectAtIndex:1] intValue]);
844 ++ for (NSString *wid in windowIds) {
845 ++ [origins_ setObject:[NSValue valueWithPoint:origin]
846 ++ forKey:[NSNumber numberWithInt:[wid intValue]]];
847 ++ }
848 ++ }
849 ++ }
850 + }
851 +
852 + - (NSString *)shortStringForHotkeyDictionary:(NSDictionary *)dict paneID:(int)wp {
853 +@@ -1851,7 +1887,8 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
854 + return [parts componentsJoinedByString:@" "];
855 + }
856 +
857 +-- (void)getHotkeysResponse:(NSString *)result {
858 ++- (void)getHotkeysResponse:(NSString *)encodedResult {
859 ++ NSString *result = [self decodedString:encodedResult optionalPrefix:iTermTmuxControllerEncodingPrefixHotkeys];
860 + [_hotkeys removeAllObjects];
861 + if (result.length > 0) {
862 + [_hotkeys removeAllObjects];
863 +@@ -1870,7 +1907,9 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
864 + }
865 + }
866 +
867 +-- (void)getTabColorsResponse:(NSString *)result {
868 ++- (void)getTabColorsResponse:(NSString *)encodedResult {
869 ++ NSString *result = [self decodedString:encodedResult
870 ++ optionalPrefix:iTermTmuxControllerEncodingPrefixTabColors];
871 + [_tabColors removeAllObjects];
872 + if (result.length > 0) {
873 + [_tabColors removeAllObjects];
874 +@@ -1903,14 +1942,15 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
875 + response = @"";
876 + }
877 + TSVDocument *doc = [response tsvDocumentWithFields:[self listWindowFields]];
878 +- id object = [userData objectAtIndex:0];
879 +- SEL selector = NSSelectorFromString([userData objectAtIndex:1]);
880 +- id target = [userData objectAtIndex:2];
881 ++ id object = userData[0];
882 ++ SEL selector = NSSelectorFromString(userData[1]);
883 ++ id target = userData[2];
884 + [target performSelector:selector withObject:doc withObject:object];
885 + }
886 +
887 +-- (void)getHiddenWindowsResponse:(NSString *)response
888 +-{
889 ++- (void)getHiddenWindowsResponse:(NSString *)encodedResponse {
890 ++ NSString *response = [self decodedString:encodedResponse
891 ++ optionalPrefix:iTermTmuxControllerEncodingPrefixHidden];
892 + [hiddenWindows_ removeAllObjects];
893 + if ([response length] > 0) {
894 + NSArray *windowIds = [response componentsSeparatedByString:@","];
895 +@@ -1922,7 +1962,7 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
896 + }
897 +
898 + - (void)getAffinitiesResponse:(NSString *)result {
899 +- [self setAffinitiesFromString:result];
900 ++ [self setAffinitiesFromString:[self decodedString:result optionalPrefix:iTermTmuxControllerEncodingPrefixAffinities]];
901 + }
902 +
903 + - (NSArray *)componentsOfAffinities:(NSString *)affinities {
904 +@@ -1991,9 +2031,28 @@ static NSDictionary *iTermTmuxControllerDefaultFontOverridesFromProfile(Profile
905 +
906 + - (void)listSessionsResponse:(NSString *)result
907 + {
908 +- self.sessions = [result componentsSeparatedByRegex:@"\n"];
909 ++ self.sessionObjects = [[result componentsSeparatedByRegex:@"\n"] mapWithBlock:^iTermTmuxSessionObject *(NSString *line) {
910 ++ const NSInteger space = [line rangeOfString:@" "].location;
911 ++ if (space == NSNotFound) {
912 ++ return nil;
913 ++ }
914 ++ NSString *sessionID = [line substringToIndex:space];
915 ++ NSString *sessionName = [line substringFromIndex:space + 1];
916 ++ if (![sessionID hasPrefix:@"$"]) {
917 ++ return nil;
918 ++ }
919 ++ NSScanner *scanner = [NSScanner scannerWithString:[sessionID substringFromIndex:1]];
920 ++ int sessionNumber = -1;
921 ++ if (![scanner scanInt:&sessionNumber] || sessionNumber < 0) {
922 ++ return nil;
923 ++ }
924 ++ iTermTmuxSessionObject *obj = [[[iTermTmuxSessionObject alloc] init] autorelease];
925 ++ obj.name = sessionName;
926 ++ obj.number = sessionNumber;
927 ++ return obj;
928 ++ }];
929 + [[NSNotificationCenter defaultCenter] postNotificationName:kTmuxControllerSessionsDidChange
930 +- object:self.sessions];
931 ++ object:nil];
932 + }
933 +
934 + - (void)listedWindowsToOpenOne:(NSString *)response
935 +diff --git sources/TmuxDashboardController.m sources/TmuxDashboardController.m
936 +index b83bfe6..b2ed7b7 100644
937 +--- sources/TmuxDashboardController.m
938 ++++ sources/TmuxDashboardController.m
939 +@@ -49,7 +49,7 @@
940 + if (self) {
941 + [self window];
942 +
943 +- [sessionsTable_ selectSessionWithName:[[self tmuxController] sessionName]];
944 ++ [sessionsTable_ selectSessionNumber:[[self tmuxController] sessionId]];
945 + [self reloadWindows];
946 + [[NSNotificationCenter defaultCenter] addObserver:self
947 + selector:@selector(tmuxControllerDetached:)
948 +@@ -140,78 +140,79 @@
949 +
950 + #pragma mark TmuxSessionsTableProtocol
951 +
952 +-- (void)renameSessionWithName:(NSString *)oldName toName:(NSString *)newName
953 +-{
954 +- [[self tmuxController] renameSession:oldName to:newName];
955 ++- (void)renameSessionWithNumber:(int)sessionNumber toName:(NSString *)newName {
956 ++ [[self tmuxController] renameSessionNumber:sessionNumber
957 ++ to:newName];
958 + }
959 +
960 +-- (void)removeSessionWithName:(NSString *)sessionName
961 +-{
962 +- [[self tmuxController] killSession:sessionName];
963 ++- (void)removeSessionWithNumber:(int)sessionNumber {
964 ++ [[self tmuxController] killSessionNumber:sessionNumber];
965 + }
966 +
967 +-- (void)addSessionWithName:(NSString *)sessionName
968 +-{
969 ++- (void)addSessionWithName:(NSString *)sessionName {
970 + [[self tmuxController] addSessionWithName:sessionName];
971 + }
972 +
973 +-- (void)attachToSessionWithName:(NSString *)sessionName
974 +-{
975 +- [[self tmuxController] attachToSession:sessionName];
976 ++- (void)attachToSessionWithNumber:(int)sessionNumber {
977 ++ [[self tmuxController] attachToSessionWithNumber:sessionNumber];
978 + }
979 +
980 +-- (void)detach
981 +-{
982 ++- (void)detach {
983 + [[self tmuxController] requestDetach];
984 + }
985 +
986 +-- (NSString *)nameOfAttachedSession
987 +-{
988 +- return [[self tmuxController] sessionName];
989 ++- (NSNumber *)numberOfAttachedSession {
990 ++ TmuxController *controller = [self tmuxController];
991 ++ if (!controller) {
992 ++ return nil;
993 ++ }
994 ++ return @([controller sessionId]);
995 + }
996 +
997 +-- (NSArray *)sessions
998 +-{
999 +- return [[self tmuxController] sessions];
1000 ++- (NSArray<iTermTmuxSessionObject *> *)sessionsTableModelValues:(id)sender {
1001 ++ return [self.tmuxController sessionObjects];
1002 + }
1003 +
1004 +-- (void)selectedSessionChangedTo:(NSString *)newSessionName
1005 +-{
1006 ++- (NSArray<iTermTmuxSessionObject *> *)sessionsTableObjects:(TmuxSessionsTable *)sender {
1007 ++ return [[self tmuxController] sessionObjects];
1008 ++}
1009 ++
1010 ++- (void)selectedSessionDidChange {
1011 + [windowsTable_ setWindows:[NSArray array]];
1012 + [self reloadWindows];
1013 + }
1014 +
1015 + - (void)linkWindowId:(int)windowId
1016 +- inSession:(NSString *)sessionName
1017 +- toSession:(NSString *)targetSession
1018 +-{
1019 ++ inSessionNumber:(int)sourceSessionNumber
1020 ++ toSessionNumber:(int)targetSessionNumber {
1021 + [[self tmuxController] linkWindowId:windowId
1022 +- inSession:sessionName
1023 +- toSession:targetSession];
1024 ++ inSessionNumber:sourceSessionNumber
1025 ++ toSessionNumber:targetSessionNumber];
1026 + }
1027 +
1028 + - (void)moveWindowId:(int)windowId
1029 +- inSession:(NSString *)sessionName
1030 +- toSession:(NSString *)targetSession
1031 +-{
1032 ++ inSessionNumber:(int)sessionNumber
1033 ++ toSessionNumber:(int)targetSessionNumber {
1034 + [[self tmuxController] moveWindowId:windowId
1035 +- inSession:sessionName
1036 +- toSession:targetSession];
1037 ++ inSessionNumber:sessionNumber
1038 ++ toSessionNumber:targetSessionNumber];
1039 + }
1040 +
1041 + #pragma mark TmuxWindowsTableProtocol
1042 +
1043 +-- (void)reloadWindows
1044 +-{
1045 +- [[self tmuxController] listWindowsInSession:[sessionsTable_ selectedSessionName]
1046 +- target:self
1047 +- selector:@selector(setWindows:forSession:)
1048 +- object:[sessionsTable_ selectedSessionName]];
1049 ++- (void)reloadWindows {
1050 ++ NSNumber *sessionNumber = [sessionsTable_ selectedSessionNumber];
1051 ++ if (!sessionNumber) {
1052 ++ return;
1053 ++ }
1054 ++ [[self tmuxController] listWindowsInSessionNumber:sessionNumber.intValue
1055 ++ target:self
1056 ++ selector:@selector(setWindows:forSession:)
1057 ++ object:[sessionsTable_ selectedSessionNumber]];
1058 + }
1059 +
1060 +-- (void)setWindows:(TSVDocument *)doc forSession:(NSString *)sessionName
1061 +-{
1062 +- if ([sessionName isEqualToString:[sessionsTable_ selectedSessionName]]) {
1063 ++- (void)setWindows:(TSVDocument *)doc forSession:(NSNumber *)sessionNumber {
1064 ++ if ([sessionNumber isEqual:[sessionsTable_ selectedSessionNumber]]) {
1065 + NSMutableArray *windows = [NSMutableArray array];
1066 + for (NSArray *record in doc.records) {
1067 + [windows addObject:[NSMutableArray arrayWithObjects:
1068 +@@ -223,18 +224,19 @@
1069 + }
1070 + }
1071 +
1072 +-- (void)renameWindowWithId:(int)windowId toName:(NSString *)newName
1073 +-{
1074 ++- (void)renameWindowWithId:(int)windowId toName:(NSString *)newName {
1075 ++ NSNumber *sessionNumber = [sessionsTable_ selectedSessionNumber];
1076 ++ if (!sessionNumber) {
1077 ++ return;
1078 ++ }
1079 + [[self tmuxController] renameWindowWithId:windowId
1080 +- inSession:[sessionsTable_ selectedSessionName]
1081 ++ inSessionNumber:sessionNumber
1082 + toName:newName];
1083 + [self reloadWindows];
1084 + }
1085 +
1086 +-- (void)unlinkWindowWithId:(int)windowId
1087 +-{
1088 +- [[self tmuxController] unlinkWindowWithId:windowId
1089 +- inSession:[sessionsTable_ selectedSessionName]];
1090 ++- (void)unlinkWindowWithId:(int)windowId {
1091 ++ [[self tmuxController] unlinkWindowWithId:windowId];
1092 + [self reloadWindows];
1093 + }
1094 +
1095 +@@ -242,15 +244,14 @@
1096 + NSString *lastName = [[windowsTable_ names] lastObject];
1097 + if (lastName) {
1098 + TmuxController *tmuxController = self.tmuxController;
1099 +- [tmuxController newWindowInSession:[sessionsTable_ selectedSessionName]
1100 +- scope:[iTermVariableScope globalsScope]
1101 +- initialDirectory:[iTermInitialDirectory initialDirectoryFromProfile:tmuxController.sharedProfile
1102 +- objectType:iTermWindowObject]];
1103 ++ [tmuxController newWindowInSessionNumber:[sessionsTable_ selectedSessionNumber]
1104 ++ scope:[iTermVariableScope globalsScope]
1105 ++ initialDirectory:[iTermInitialDirectory initialDirectoryFromProfile:tmuxController.sharedProfile
1106 ++ objectType:iTermWindowObject]];
1107 + }
1108 + }
1109 +
1110 +-- (void)showWindowsWithIds:(NSArray *)windowIds inTabs:(BOOL)inTabs
1111 +-{
1112 ++- (void)showWindowsWithIds:(NSArray *)windowIds inTabs:(BOOL)inTabs {
1113 + if (inTabs) {
1114 + for (NSNumber *wid in windowIds) {
1115 + [[self tmuxController] openWindowWithId:[wid intValue]
1116 +@@ -265,27 +266,24 @@
1117 + profile:self.tmuxController.sharedProfile];
1118 + }
1119 + }
1120 +- [[self tmuxController] saveHiddenWindows];
1121 ++ [[self tmuxController] saveHiddenWindows];
1122 + }
1123 +
1124 + - (void)hideWindowWithId:(int)windowId
1125 + {
1126 +- [[self tmuxController] hideWindow:windowId];
1127 ++ [[self tmuxController] hideWindow:windowId];
1128 + [windowsTable_ updateEnabledStateOfButtons];
1129 + }
1130 +
1131 +-- (BOOL)haveSelectedSession
1132 +-{
1133 +- return [sessionsTable_ selectedSessionName] != nil;
1134 ++- (BOOL)haveSelectedSession {
1135 ++ return [sessionsTable_ selectedSessionNumber] != nil;
1136 + }
1137 +
1138 +-- (BOOL)currentSessionSelected
1139 +-{
1140 +- return [[sessionsTable_ selectedSessionName] isEqualToString:[[self tmuxController] sessionName]];
1141 ++- (BOOL)currentSessionSelected {
1142 ++ return [[sessionsTable_ selectedSessionNumber] isEqual:@([[self tmuxController] sessionId])];
1143 + }
1144 +
1145 +-- (BOOL)haveOpenWindowWithId:(int)windowId
1146 +-{
1147 ++- (BOOL)haveOpenWindowWithId:(int)windowId {
1148 + return [[self tmuxController] window:windowId] != nil;
1149 + }
1150 +
1151 +@@ -294,48 +292,41 @@
1152 + [tab.activeSession reveal];
1153 + }
1154 +
1155 +-- (NSString *)selectedSessionName
1156 +-{
1157 +- return [sessionsTable_ selectedSessionName];
1158 ++- (NSNumber *)selectedSessionNumber {
1159 ++ return [sessionsTable_ selectedSessionNumber];
1160 + }
1161 +
1162 + #pragma mark - Private
1163 +
1164 +-- (void)tmuxControllerDetached:(NSNotification *)notification
1165 +-{
1166 +- [sessionsTable_ setSessions:[NSArray array]];
1167 ++- (void)tmuxControllerDetached:(NSNotification *)notification {
1168 ++ [sessionsTable_ setSessionObjects:@[]];
1169 + }
1170 +
1171 +-- (void)tmuxControllerSessionsDidChange:(NSNotification *)notification
1172 +-{
1173 +- [sessionsTable_ setSessions:[[self tmuxController] sessions]];
1174 ++- (void)tmuxControllerSessionsDidChange:(NSNotification *)notification {
1175 ++ [sessionsTable_ setSessionObjects:[[self tmuxController] sessionObjects]];
1176 + }
1177 +
1178 +-- (void)tmuxControllerWindowsDidChange:(NSNotification *)notification
1179 +-{
1180 ++- (void)tmuxControllerWindowsDidChange:(NSNotification *)notification {
1181 + if ([[self window] isVisible]) {
1182 + [self reloadWindows];
1183 + }
1184 + }
1185 +
1186 +-- (void)tmuxControllerAttachedSessionChanged:(NSNotification *)notification
1187 +-{
1188 ++- (void)tmuxControllerAttachedSessionChanged:(NSNotification *)notification {
1189 + if ([[self window] isVisible]) {
1190 +- [sessionsTable_ selectSessionWithName:[[self tmuxController] sessionName]];
1191 ++ [sessionsTable_ selectSessionNumber:[[self tmuxController] sessionId]];
1192 + [windowsTable_ updateEnabledStateOfButtons];
1193 + }
1194 + }
1195 +
1196 +-- (void)tmuxControllerWindowOpenedOrClosed:(NSNotification *)notification
1197 +-{
1198 ++- (void)tmuxControllerWindowOpenedOrClosed:(NSNotification *)notification {
1199 + if ([[self window] isVisible]) {
1200 + [windowsTable_ updateEnabledStateOfButtons];
1201 + [windowsTable_ reloadData];
1202 + }
1203 + }
1204 +
1205 +-- (void)tmuxControllerWindowWasRenamed:(NSNotification *)notification
1206 +-{
1207 ++- (void)tmuxControllerWindowWasRenamed:(NSNotification *)notification {
1208 + if ([[self window] isVisible]) {
1209 + NSArray *objects = [notification object];
1210 + int wid = [[objects objectAtIndex:0] intValue];
1211 +@@ -344,8 +335,7 @@
1212 + }
1213 + }
1214 +
1215 +-- (void)tmuxControllerSessionWasRenamed:(NSNotification *)notification
1216 +-{
1217 ++- (void)tmuxControllerSessionWasRenamed:(NSNotification *)notification {
1218 + // This is a bit of extra work but the sessions table wasn't built knowing about session IDs.
1219 + [[self tmuxController] listSessions];
1220 + }
1221 +@@ -364,8 +354,7 @@
1222 + [self connectionSelectionDidChange:nil];
1223 + }
1224 +
1225 +-- (TmuxController *)tmuxController
1226 +-{
1227 ++- (TmuxController *)tmuxController {
1228 + return [[TmuxControllerRegistry sharedInstance] controllerForClient:[self currentClient]]; // TODO: track the current client when multiples are supported
1229 + }
1230 +
1231 +@@ -375,7 +364,7 @@
1232 +
1233 +
1234 + - (IBAction)connectionSelectionDidChange:(id)sender {
1235 +- [sessionsTable_ setSessions:[[self tmuxController] sessions]];
1236 ++ [sessionsTable_ setSessionObjects:[[self tmuxController] sessionObjects]];
1237 + [self reloadWindows];
1238 + }
1239 +
1240 +diff --git sources/TmuxSessionsTable.h sources/TmuxSessionsTable.h
1241 +index 856f0f5..a3e5823 100644
1242 +--- sources/TmuxSessionsTable.h
1243 ++++ sources/TmuxSessionsTable.h
1244 +@@ -9,21 +9,25 @@
1245 + #import <Cocoa/Cocoa.h>
1246 + #import "FutureMethods.h"
1247 +
1248 ++@class TmuxSessionsTable;
1249 ++@class iTermTmuxSessionObject;
1250 ++
1251 + @protocol TmuxSessionsTableProtocol <NSObject>
1252 +
1253 +-- (NSArray *)sessions;
1254 +-- (void)renameSessionWithName:(NSString *)oldName toName:(NSString *)newName;
1255 +-- (void)removeSessionWithName:(NSString *)sessionName;
1256 ++- (NSArray<iTermTmuxSessionObject *> *)sessionsTableObjects:(TmuxSessionsTable *)sender;
1257 ++- (void)renameSessionWithNumber:(int)sessionNumber
1258 ++ toName:(NSString *)newName;
1259 ++- (void)removeSessionWithNumber:(int)sessionNumber;
1260 + - (void)addSessionWithName:(NSString *)sessionName;
1261 +-- (void)attachToSessionWithName:(NSString *)sessionName;
1262 +-- (NSString *)nameOfAttachedSession;
1263 +-- (void)selectedSessionChangedTo:(NSString *)newName;
1264 ++- (void)attachToSessionWithNumber:(int)sessionNumber;
1265 ++- (NSNumber *)numberOfAttachedSession;
1266 ++- (void)selectedSessionDidChange;
1267 + - (void)linkWindowId:(int)windowId
1268 +- inSession:(NSString *)sessionName
1269 +- toSession:(NSString *)targetSession;
1270 ++ inSessionNumber:(int)sourceSessionNumber
1271 ++ toSessionNumber:(int)targetSessionNumber;
1272 + - (void)moveWindowId:(int)windowId
1273 +- inSession:(NSString *)sessionName
1274 +- toSession:(NSString *)targetSession;
1275 ++ inSessionNumber:(int)sessionNumber
1276 ++ toSessionNumber:(int)targetSessionNumber;
1277 + - (void)detach;
1278 +
1279 + @end
1280 +@@ -31,9 +35,9 @@
1281 + @interface TmuxSessionsTable : NSObject <NSTableViewDelegate, NSTableViewDataSource>
1282 +
1283 + @property(nonatomic, assign) id<TmuxSessionsTableProtocol> delegate;
1284 +-@property(nonatomic, readonly) NSString *selectedSessionName;
1285 ++@property(nonatomic, readonly) NSNumber *selectedSessionNumber;
1286 +
1287 +-- (void)setSessions:(NSArray *)names;
1288 +-- (void)selectSessionWithName:(NSString *)name;
1289 ++- (void)setSessionObjects:(NSArray<iTermTmuxSessionObject *> *)names;
1290 ++- (void)selectSessionNumber:(int)number;
1291 +
1292 + @end
1293 +diff --git sources/TmuxSessionsTable.m sources/TmuxSessionsTable.m
1294 +index f9a086f..88e2f72 100644
1295 +--- sources/TmuxSessionsTable.m
1296 ++++ sources/TmuxSessionsTable.m
1297 +@@ -7,12 +7,15 @@
1298 + //
1299 +
1300 + #import "TmuxSessionsTable.h"
1301 ++
1302 + #import "FutureMethods.h"
1303 ++#import "iTermTmuxSessionObject.h"
1304 ++#import "NSArray+iTerm.h"
1305 +
1306 + extern NSString *kWindowPasteboardType;
1307 +
1308 + @implementation TmuxSessionsTable {
1309 +- NSMutableArray *model_;
1310 ++ NSMutableArray<iTermTmuxSessionObject *> *_model;
1311 + BOOL canAttachToSelectedSession_;
1312 +
1313 + IBOutlet NSTableColumn *checkColumn_;
1314 +@@ -28,7 +31,7 @@ extern NSString *kWindowPasteboardType;
1315 + - (instancetype)init {
1316 + self = [super init];
1317 + if (self) {
1318 +- model_ = [[NSMutableArray alloc] init];
1319 ++ _model = [[NSMutableArray alloc] init];
1320 + }
1321 + return self;
1322 + }
1323 +@@ -39,27 +42,27 @@ extern NSString *kWindowPasteboardType;
1324 + [tableView_ setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleRegular];
1325 + }
1326 +
1327 +-- (void)dealloc
1328 +-{
1329 +- [model_ release];
1330 ++- (void)dealloc {
1331 ++ [_model release];
1332 + [super dealloc];
1333 + }
1334 +
1335 + - (void)setDelegate:(id<TmuxSessionsTableProtocol>)delegate {
1336 + delegate_ = delegate;
1337 +- [self setSessions:[delegate_ sessions]];
1338 ++ [self setSessionObjects:[delegate_ sessionsTableObjects:self]];
1339 + }
1340 +
1341 +-- (void)setSessions:(NSArray *)names
1342 ++- (void)setSessionObjects:(NSArray<iTermTmuxSessionObject *> *)sessions
1343 + {
1344 +- [model_ removeAllObjects];
1345 +- [model_ addObjectsFromArray:names];
1346 ++ [_model removeAllObjects];
1347 ++ [_model addObjectsFromArray:sessions];
1348 + [tableView_ reloadData];
1349 + }
1350 +
1351 +-- (void)selectSessionWithName:(NSString *)name
1352 +-{
1353 +- NSUInteger i = [model_ indexOfObject:name];
1354 ++- (void)selectSessionNumber:(int)number {
1355 ++ NSInteger i = [_model indexOfObjectPassingTest:^BOOL(iTermTmuxSessionObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
1356 ++ return obj.number == number;
1357 ++ }];
1358 + if (i != NSNotFound) {
1359 + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:i]
1360 + byExtendingSelection:NO];
1361 +@@ -74,47 +77,45 @@ extern NSString *kWindowPasteboardType;
1362 +
1363 + - (IBAction)removeSession:(id)sender
1364 + {
1365 +- NSString *name = [self selectedSessionName];
1366 +- if (name) {
1367 +- [delegate_ removeSessionWithName:name];
1368 ++ NSNumber *number = [self selectedSessionNumber];
1369 ++ if (number) {
1370 ++ [delegate_ removeSessionWithNumber:number.intValue];
1371 + }
1372 + }
1373 +
1374 + - (IBAction)attach:(id)sender {
1375 +- NSString *name = [self selectedSessionName];
1376 +- if (name) {
1377 +- [delegate_ attachToSessionWithName:name];
1378 ++ NSNumber *number = [self selectedSessionNumber];
1379 ++ if (number) {
1380 ++ [delegate_ attachToSessionWithNumber:number.intValue];
1381 + }
1382 + }
1383 +
1384 + - (IBAction)detach:(id)sender {
1385 +- NSString *name = [self selectedSessionName];
1386 +- if (name) {
1387 ++ NSNumber *number = [self selectedSessionNumber];
1388 ++ if (number) {
1389 + [delegate_ detach];
1390 + }
1391 + }
1392 +
1393 + #pragma mark NSTableViewDataSource
1394 +
1395 +-- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
1396 +-{
1397 +- return model_.count;
1398 ++- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
1399 ++ return _model.count;
1400 + }
1401 +
1402 + - (id)tableView:(NSTableView *)aTableView
1403 + objectValueForTableColumn:(NSTableColumn *)aTableColumn
1404 +- row:(NSInteger)rowIndex
1405 +-{
1406 +- NSString *name = [model_ objectAtIndex:rowIndex];
1407 ++ row:(NSInteger)rowIndex {
1408 ++ iTermTmuxSessionObject *sessionObject = _model[rowIndex];
1409 + if (aTableColumn == checkColumn_) {
1410 +- if ([[delegate_ nameOfAttachedSession] isEqualToString:name]) {
1411 ++ if ([[delegate_ numberOfAttachedSession] isEqual:@(sessionObject.number)]) {
1412 + return @"✓";
1413 + } else {
1414 + return @"";
1415 + }
1416 + } else {
1417 +- if (rowIndex < model_.count) {
1418 +- return name;
1419 ++ if (rowIndex < _model.count) {
1420 ++ return sessionObject.name;
1421 + } else {
1422 + return nil;
1423 + }
1424 +@@ -124,10 +125,9 @@ extern NSString *kWindowPasteboardType;
1425 + - (void)tableView:(NSTableView *)aTableView
1426 + setObjectValue:(id)anObject
1427 + forTableColumn:(NSTableColumn *)aTableColumn
1428 +- row:(NSInteger)rowIndex
1429 +-{
1430 +- [delegate_ renameSessionWithName:[model_ objectAtIndex:rowIndex]
1431 +- toName:(NSString *)anObject];
1432 ++ row:(NSInteger)rowIndex {
1433 ++ [delegate_ renameSessionWithNumber:_model[rowIndex].number
1434 ++ toName:(NSString *)anObject];
1435 + }
1436 +
1437 + #pragma mark NSTableViewDataSource
1438 +@@ -138,16 +138,15 @@ extern NSString *kWindowPasteboardType;
1439 + return YES;
1440 + }
1441 +
1442 +-- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
1443 +-{
1444 ++- (void)tableViewSelectionDidChange:(NSNotification *)aNotification {
1445 + [self updateEnabledStateOfButtons];
1446 +- [delegate_ selectedSessionChangedTo:[self selectedSessionName]];
1447 ++ [delegate_ selectedSessionDidChange];
1448 + }
1449 +
1450 +-- (NSString *)selectedSessionName {
1451 ++- (NSNumber *)selectedSessionNumber {
1452 + int i = [tableView_ selectedRow];
1453 +- if (i >= 0 && i < model_.count) {
1454 +- return [model_ objectAtIndex:i];
1455 ++ if (i >= 0 && i < _model.count) {
1456 ++ return @(_model[i].number);
1457 + } else {
1458 + return nil;
1459 + }
1460 +@@ -159,19 +158,19 @@ extern NSString *kWindowPasteboardType;
1461 + dropOperation:(NSTableViewDropOperation)operation {
1462 + NSPasteboard *pb = [info draggingPasteboard];
1463 + NSArray* pair = [pb propertyListForType:kWindowPasteboardType];
1464 +- NSString *sessionName = [pair objectAtIndex:0];
1465 +- NSArray *draggedItems = [pair objectAtIndex:1];
1466 +- NSString *targetSession = [model_ objectAtIndex:row];
1467 ++ NSNumber *sessionNumber = pair[0];
1468 ++ NSArray *draggedItems = pair[1];
1469 ++ iTermTmuxSessionObject *targetSessionObject = _model[row];
1470 + for (NSArray *tuple in draggedItems) {
1471 + NSNumber *windowId = [tuple objectAtIndex:1];
1472 + if (info.draggingSourceOperationMask & NSDragOperationLink) {
1473 + [delegate_ linkWindowId:[windowId intValue]
1474 +- inSession:sessionName
1475 +- toSession:targetSession];
1476 ++ inSessionNumber:sessionNumber.intValue
1477 ++ toSessionNumber:targetSessionObject.number];
1478 + } else {
1479 + [delegate_ moveWindowId:[windowId intValue]
1480 +- inSession:sessionName
1481 +- toSession:targetSession];
1482 ++ inSessionNumber:sessionNumber.intValue
1483 ++ toSessionNumber:targetSessionObject.number];
1484 + }
1485 + }
1486 + return YES;
1487 +@@ -205,11 +204,16 @@ extern NSString *kWindowPasteboardType;
1488 + }
1489 + }
1490 +
1491 +-- (NSString *)nameForNewSession
1492 +-{
1493 ++- (BOOL)haveSessionWithName:(NSString *)name {
1494 ++ return [_model anyWithBlock:^BOOL(iTermTmuxSessionObject *anObject) {
1495 ++ return [anObject.name isEqualToString:name];
1496 ++ }];
1497 ++}
1498 ++
1499 ++- (NSString *)nameForNewSession {
1500 + int n = 0;
1501 + NSString *candidate = [self nameForNewSessionWithNumber:n];
1502 +- while ([model_ indexOfObject:candidate] != NSNotFound) {
1503 ++ while ([self haveSessionWithName:candidate]) {
1504 + n++;
1505 + candidate = [self nameForNewSessionWithNumber:n];
1506 + }
1507 +@@ -223,7 +227,9 @@ extern NSString *kWindowPasteboardType;
1508 + [detachButton_ setEnabled:NO];
1509 + [removeButton_ setEnabled:NO];
1510 + } else {
1511 +- BOOL isAttachedSession = [[delegate_ nameOfAttachedSession] isEqualToString:[self selectedSessionName]];
1512 ++ NSNumber *selected = [self selectedSessionNumber];
1513 ++ BOOL isAttachedSession = (selected != nil &&
1514 ++ [[delegate_ numberOfAttachedSession] isEqual:@(selected.intValue)]);
1515 + [attachButton_ setEnabled:!isAttachedSession];
1516 + [detachButton_ setEnabled:isAttachedSession];
1517 + [removeButton_ setEnabled:YES];
1518 +diff --git sources/TmuxWindowsTable.h sources/TmuxWindowsTable.h
1519 +index 7c3e16c..6e9604e 100644
1520 +--- sources/TmuxWindowsTable.h
1521 ++++ sources/TmuxWindowsTable.h
1522 +@@ -22,7 +22,7 @@ extern NSString *kWindowPasteboardType;
1523 + - (BOOL)haveSelectedSession;
1524 + - (BOOL)currentSessionSelected;
1525 + - (BOOL)haveOpenWindowWithId:(int)windowId;
1526 +-- (NSString *)selectedSessionName;
1527 ++- (NSNumber *)selectedSessionNumber;
1528 + - (void)tmuxWindowsTableDidSelectWindowWithId:(int)windowId;
1529 +
1530 + @end
1531 +diff --git sources/TmuxWindowsTable.m sources/TmuxWindowsTable.m
1532 +index 56f4954..8f92993 100644
1533 +--- sources/TmuxWindowsTable.m
1534 ++++ sources/TmuxWindowsTable.m
1535 +@@ -173,9 +173,9 @@ NSString *kWindowPasteboardType = @"kWindowPasteboardType";
1536 + - (void)tableView:(NSTableView *)aTableView
1537 + setObjectValue:(id)anObject
1538 + forTableColumn:(NSTableColumn *)aTableColumn
1539 +- row:(NSInteger)rowIndex
1540 +-{
1541 +- [delegate_ renameWindowWithId:[[[[self filteredModel] objectAtIndex:rowIndex] objectAtIndex:1] intValue]
1542 ++ row:(NSInteger)rowIndex {
1543 ++ const int windowID = [[[[self filteredModel] objectAtIndex:rowIndex] objectAtIndex:1] intValue];
1544 ++ [delegate_ renameWindowWithId:windowID
1545 + toName:anObject];
1546 + }
1547 +
1548 +@@ -200,7 +200,7 @@ NSString *kWindowPasteboardType = @"kWindowPasteboardType";
1549 + NSArray* selectedItems = [[self filteredModel] objectsAtIndexes:rowIndexes];
1550 + [pboard declareTypes:[NSArray arrayWithObject:kWindowPasteboardType] owner:self];
1551 + [pboard setPropertyList:[NSArray arrayWithObjects:
1552 +- [delegate_ selectedSessionName],
1553 ++ [delegate_ selectedSessionNumber],
1554 + selectedItems,
1555 + nil]
1556 + forType:kWindowPasteboardType];
1557 +@@ -219,14 +219,13 @@ NSString *kWindowPasteboardType = @"kWindowPasteboardType";
1558 +
1559 + #pragma mark - Private
1560 +
1561 +-- (NSArray *)selectedWindowIdsAsStrings
1562 +-{
1563 +- NSArray *ids = [self selectedWindowIds];
1564 ++- (NSArray *)selectedWindowIdsAsStrings {
1565 ++ NSArray *ids = [self selectedWindowIds];
1566 + NSMutableArray *result = [NSMutableArray array];
1567 +- for (NSString *n in ids) {
1568 +- [result addObject:n];
1569 +- }
1570 +- return result;
1571 ++ for (NSString *n in ids) {
1572 ++ [result addObject:n];
1573 ++ }
1574 ++ return result;
1575 + }
1576 +
1577 + - (NSArray *)selectedWindowIds
1578 +diff --git sources/iTermInitialDirectory+Tmux.h sources/iTermInitialDirectory+Tmux.h
1579 +index a470a56..586ade6 100644
1580 +--- sources/iTermInitialDirectory+Tmux.h
1581 ++++ sources/iTermInitialDirectory+Tmux.h
1582 +@@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
1583 +
1584 + @interface iTermInitialDirectory (Tmux)
1585 +
1586 +-- (void)tmuxNewWindowCommandInSession:(nullable NSString *)session
1587 ++- (void)tmuxNewWindowCommandInSessionNumber:(nullable NSNumber *)sessionNumber
1588 + recyclingSupported:(BOOL)recyclingSupported
1589 + scope:(iTermVariableScope *)scope
1590 + completion:(void (^)(NSString *))completion;
1591 +diff --git sources/iTermInitialDirectory+Tmux.m sources/iTermInitialDirectory+Tmux.m
1592 +index a67bea4..3259eda 100644
1593 +--- sources/iTermInitialDirectory+Tmux.m
1594 ++++ sources/iTermInitialDirectory+Tmux.m
1595 +@@ -11,14 +11,14 @@
1596 +
1597 + @implementation iTermInitialDirectory(Tmux)
1598 +
1599 +-- (void)tmuxNewWindowCommandInSession:(NSString *)session
1600 +- recyclingSupported:(BOOL)recyclingSupported
1601 +- scope:(iTermVariableScope *)scope
1602 +- completion:(void (^)(NSString *))completion {
1603 ++- (void)tmuxNewWindowCommandInSessionNumber:(nullable NSNumber *)sessionNumber
1604 ++ recyclingSupported:(BOOL)recyclingSupported
1605 ++ scope:(iTermVariableScope *)scope
1606 ++ completion:(void (^)(NSString *))completion {
1607 + NSArray *args = @[ @"new-window", @"-PF '#{window_id}'" ];
1608 +
1609 +- if (session) {
1610 +- NSString *targetSessionArg = [NSString stringWithFormat:@"\"%@:+\"", [session stringByEscapingQuotes]];
1611 ++ if (sessionNumber) {
1612 ++ NSString *targetSessionArg = [NSString stringWithFormat:@"\"$%d:+\"", sessionNumber.intValue];
1613 + NSArray *insertionArguments = @[ @"-a",
1614 + @"-t",
1615 + targetSessionArg ];
1616 +@@ -33,10 +33,10 @@
1617 + - (void)tmuxNewWindowCommandRecyclingSupported:(BOOL)recyclingSupported
1618 + scope:(iTermVariableScope *)scope
1619 + completion:(void (^)(NSString *))completion {
1620 +- [self tmuxNewWindowCommandInSession:nil
1621 +- recyclingSupported:recyclingSupported
1622 +- scope:scope
1623 +- completion:completion];
1624 ++ [self tmuxNewWindowCommandInSessionNumber:nil
1625 ++ recyclingSupported:recyclingSupported
1626 ++ scope:scope
1627 ++ completion:completion];
1628 + }
1629 +
1630 + - (void)tmuxSplitWindowCommand:(int)wp
1631 +diff --git sources/iTermTmuxOptionMonitor.h sources/iTermTmuxOptionMonitor.h
1632 +index 147fcd6..8dffe52 100644
1633 +--- sources/iTermTmuxOptionMonitor.h
1634 ++++ sources/iTermTmuxOptionMonitor.h
1635 +@@ -18,8 +18,12 @@ NS_ASSUME_NONNULL_BEGIN
1636 + @property (nullable, nonatomic, strong) iTermVariableScope *scope;
1637 +
1638 + - (instancetype)init NS_UNAVAILABLE;
1639 ++
1640 ++// If `fallbackVariableName` is nonnil, the value of the variable named
1641 ++// `fallbackVariableName` will be used for tmux 2.8 and earlier.
1642 + - (instancetype)initWithGateway:(TmuxGateway *)gateway
1643 + scope:(iTermVariableScope *)scope
1644 ++ fallbackVariableName:(nullable NSString *)fallbackVariableName
1645 + format:(NSString *)format
1646 + target:(NSString *)tmuxTarget
1647 + variableName:(nullable NSString *)variableName
1648 +diff --git sources/iTermTmuxOptionMonitor.m sources/iTermTmuxOptionMonitor.m
1649 +index 4f72d02..fd6120f 100644
1650 +--- sources/iTermTmuxOptionMonitor.m
1651 ++++ sources/iTermTmuxOptionMonitor.m
1652 +@@ -10,6 +10,7 @@
1653 + #import "DebugLogging.h"
1654 + #import "iTermVariables.h"
1655 + #import "iTermVariableScope+Session.h"
1656 ++#import "NSStringITerm.h"
1657 + #import "NSTimer+iTerm.h"
1658 + #import "TmuxGateway.h"
1659 +
1660 +@@ -19,11 +20,13 @@
1661 + BOOL _haveOutstandingRequest;
1662 + NSString *_target;
1663 + NSString *_variableName;
1664 ++ NSString *_fallbackVariableName;
1665 + void (^_block)(NSString *);
1666 + }
1667 +
1668 + - (instancetype)initWithGateway:(TmuxGateway *)gateway
1669 + scope:(iTermVariableScope *)scope
1670 ++ fallbackVariableName:(NSString *)fallbackVariableName
1671 + format:(NSString *)format
1672 + target:(NSString *)target
1673 + variableName:(NSString *)variableName
1674 +@@ -36,6 +39,7 @@
1675 + _target = [target copy];
1676 + _variableName = [variableName copy];
1677 + _block = [block copy];
1678 ++ _fallbackVariableName = [fallbackVariableName copy];
1679 + }
1680 + return self;
1681 + }
1682 +@@ -64,13 +68,21 @@
1683 + [self updateOnce];
1684 + }
1685 +
1686 ++- (NSString *)command {
1687 ++ return [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat];
1688 ++}
1689 ++
1690 + - (void)updateOnce {
1691 + if (_haveOutstandingRequest) {
1692 + DLog(@"Not making a request because one is outstanding");
1693 + return;
1694 + }
1695 ++ if (_fallbackVariableName && self.gateway.minimumServerVersion.doubleValue <= 2.9) {
1696 ++ [self didFetch:[self.scope valueForVariableName:_fallbackVariableName]];
1697 ++ return;
1698 ++ }
1699 + _haveOutstandingRequest = YES;
1700 +- NSString *command = [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat];
1701 ++ NSString *command = [self command];
1702 + DLog(@"Request option with command %@", command);
1703 + [self.gateway sendCommand:command
1704 + responseTarget:self
1705 +@@ -80,7 +92,7 @@
1706 + }
1707 +
1708 + - (void)didFetch:(NSString *)value {
1709 +- DLog(@"Did fetch %@", value);
1710 ++ DLog(@"%@ -> %@", self.command, value);
1711 + if (!value) {
1712 + // Probably the pane went away and we'll be dealloced soon.
1713 + return;
1714 +diff --git sources/iTermTmuxSessionObject.h sources/iTermTmuxSessionObject.h
1715 +new file mode 100644
1716 +index 0000000..6407d5f
1717 +--- /dev/null
1718 ++++ sources/iTermTmuxSessionObject.h
1719 +@@ -0,0 +1,17 @@
1720 ++//
1721 ++// iTermTmuxSessionObject.h
1722 ++// iTerm2SharedARC
1723 ++//
1724 ++// Created by George Nachman on 9/25/19.
1725 ++//
1726 ++
1727 ++#import <Foundation/Foundation.h>
1728 ++
1729 ++NS_ASSUME_NONNULL_BEGIN
1730 ++
1731 ++@interface iTermTmuxSessionObject : NSObject
1732 ++@property (nonatomic, copy) NSString *name;
1733 ++@property (nonatomic) int number;
1734 ++@end
1735 ++
1736 ++NS_ASSUME_NONNULL_END
1737 +diff --git sources/iTermTmuxSessionObject.m sources/iTermTmuxSessionObject.m
1738 +new file mode 100644
1739 +index 0000000..5ac9130
1740 +--- /dev/null
1741 ++++ sources/iTermTmuxSessionObject.m
1742 +@@ -0,0 +1,12 @@
1743 ++//
1744 ++// iTermTmuxSessionObject.m
1745 ++// iTerm2SharedARC
1746 ++//
1747 ++// Created by George Nachman on 9/25/19.
1748 ++//
1749 ++
1750 ++#import "iTermTmuxSessionObject.h"
1751 ++
1752 ++@implementation iTermTmuxSessionObject
1753 ++
1754 ++@end
1755 +diff --git sources/iTermTmuxStatusBarMonitor.h sources/iTermTmuxStatusBarMonitor.h
1756 +index 8172779..96338ec 100644
1757 +--- sources/iTermTmuxStatusBarMonitor.h
1758 ++++ sources/iTermTmuxStatusBarMonitor.h
1759 +@@ -14,6 +14,7 @@
1760 +
1761 + NS_ASSUME_NONNULL_BEGIN
1762 +
1763 ++// NOTE: Requires tmux 2.9
1764 + @interface iTermTmuxStatusBarMonitor : NSObject
1765 +
1766 + @property (nonatomic) BOOL active;
1767 +diff --git sources/iTermTmuxStatusBarMonitor.m sources/iTermTmuxStatusBarMonitor.m
1768 +index 50504da..0d3daf3 100644
1769 +--- sources/iTermTmuxStatusBarMonitor.m
1770 ++++ sources/iTermTmuxStatusBarMonitor.m
1771 +@@ -9,6 +9,7 @@
1772 +
1773 + #import "DebugLogging.h"
1774 + #import "iTermVariableScope.h"
1775 ++#import "NSStringITerm.h"
1776 + #import "NSTimer+iTerm.h"
1777 + #import "TmuxGateway.h"
1778 + #import "RegexKitLite.h"
1779 +@@ -49,13 +50,8 @@
1780 +
1781 + - (void)requestUpdates {
1782 + _accelerated = NO;
1783 +- [_gateway sendCommand:@"display-message -p \"#{status-left}\"" responseTarget:self responseSelector:@selector(handleStatusLeftResponse:)];
1784 +- [_gateway sendCommand:@"display-message -p \"#{status-right}\"" responseTarget:self responseSelector:@selector(handleStatusRightResponse:)];
1785 +-}
1786 +-
1787 +-- (NSString *)escapedString:(NSString *)string {
1788 +- return [[string stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]
1789 +- stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
1790 ++ [_gateway sendCommand:@"display-message -p \"#{T:status-left}\"" responseTarget:self responseSelector:@selector(handleStatusLeftValueExpansionResponse:)];
1791 ++ [_gateway sendCommand:@"display-message -p \"#{T:status-right}\"" responseTarget:self responseSelector:@selector(handleStatusRightValueExpansionResponse:)];
1792 + }
1793 +
1794 + - (void)handleStatusIntervalResponse:(NSString *)response {
1795 +@@ -66,28 +62,14 @@
1796 + _timer = [NSTimer scheduledWeakTimerWithTimeInterval:interval target:self selector:@selector(timerDidFire:) userInfo:nil repeats:YES];
1797 + }
1798 +
1799 +-- (void)handleStatusLeftResponse:(NSString *)response {
1800 +- if (!response) {
1801 +- return;
1802 +- }
1803 +- NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]];
1804 +- [_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusLeftValueExpansionResponse:)];
1805 +-}
1806 +-
1807 +-- (void)handleStatusRightResponse:(NSString *)response {
1808 +- if (!response) {
1809 +- return;
1810 +- }
1811 +- NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]];
1812 +- [_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusRightValueExpansionResponse:)];
1813 +-}
1814 +-
1815 + - (void)handleStatusLeftValueExpansionResponse:(NSString *)string {
1816 ++ DLog(@"Left status bar is: %@", string);
1817 + [self.scope setValue:[self sanitizedString:string] ?: @"" forVariableNamed:iTermVariableKeySessionTmuxStatusLeft];
1818 + [self accelerateUpdateIfStringContainsNotReady:string];
1819 + }
1820 +
1821 + - (void)handleStatusRightValueExpansionResponse:(NSString *)string {
1822 ++ DLog(@"Right status bar is: %@", string);
1823 + [self.scope setValue:[self sanitizedString:string] ?: @"" forVariableNamed:iTermVariableKeySessionTmuxStatusRight];
1824 + [self accelerateUpdateIfStringContainsNotReady:string];
1825 + }
1826 +diff --git sources/iTermWorkingDirectoryPoller.m sources/iTermWorkingDirectoryPoller.m
1827 +index 5271227..f4d8602 100644
1828 +--- sources/iTermWorkingDirectoryPoller.m
1829 ++++ sources/iTermWorkingDirectoryPoller.m
1830 +@@ -37,6 +37,7 @@
1831 + __weak __typeof(self) weakSelf = self;
1832 + _tmuxOptionMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:gateway
1833 + scope:scope
1834 ++ fallbackVariableName:nil
1835 + format:@"#{pane_current_path}"
1836 + target:[NSString stringWithFormat:@"%%%@", @(windowPane)]
1837 + variableName:nil
1838 +--
1839 +2.23.0
1840 +

Everything looks good. We'll let you know here if there's anything you should know about.

Add shortcut