Skip to content
Snippets Groups Projects
Commit 617ad184 authored by George Nachman's avatar George Nachman
Browse files

Add a notification to the websocket server that receives custom escape

sequences. Add a python example to receive them and a shell script to send
them.
parent e40c77bd
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -22,8 +22,9 @@ pip install protobuf
It is also possible to install the dependencies as system libraries.
 
```
sudo easy_install pip
sudo /usr/bin/python -m pip install websocket-client
sudo /usr/bin/python -m pip install protobuf
sudo /usr/bin/python -m pip install --ignore-installed protobuf
```
 
Then `cd api/examples/python` and then `./iterm2.py` to run the program.
Loading
Loading
This diff is collapsed.
#!/bin/bash
# This is meant to be used a with a websocket server based on remote_control.py that can receive payloads form this command.
# tmux requires unrecognized OSC sequences to be wrapped with DCS tmux;
# <sequence> ST, and for all ESCs in <sequence> to be replaced with ESC ESC. It
# only accepts ESC backslash for ST.
function print_osc() {
if [[ $TERM == screen* ]] ; then
printf "\033Ptmux;\033\033]"
else
printf "\033]"
fi
}
# More of the tmux workaround described above.
function print_st() {
if [[ $TERM == screen* ]] ; then
printf "\a\033\\"
else
printf "\a"
fi
}
function error() {
echo "ERROR: $*" 1>&2
}
function show_help() {
echo "Usage: it2custom identity payload" 1>& 2
}
## Main
# Show help if no arguments and no stdin.
if [ $# -ne 2 ]; then
show_help
exit
fi
print_osc
printf "1337;Custom=id=%s:%s" "$1" "$2"
print_st
exit 0
#!/usr/bin/python
# This is python 2.7 on macOS 10.12.
from __future__ import print_function
import api_pb2
import sys
import thread
import time
import websocket
callbacks = []
def SendRPC(ws, message, callback):
ws.send(message.SerializeToString(), opcode=websocket.ABNF.OPCODE_BINARY)
callbacks.append(callback)
def handle_notification(notification):
def handle_custom_escape_sequence_notification(custom_escape_sequence_notification):
# -- Your logic goes here --
print(custom_escape_sequence_notification.sender_identity + " sends message " + custom_escape_sequence_notification.payload)
if notification.HasField('custom_escape_sequence_notification'):
handle_custom_escape_sequence_notification(notification.custom_escape_sequence_notification)
def subscribe_to_custom_escape_sequence(ws, session):
def handle_notification_response(response):
if not response.HasField('notification_response'):
print("Malformed notification response")
print(str(response))
return
if response.notification_response.status != api_pb2.NotificationResponse.OK:
print("Bad status in notification response")
print(str(response))
return
request = api_pb2.Request()
request.notification_request.subscribe = True
request.notification_request.session = session
request.notification_request.notification_type = api_pb2.NOTIFY_ON_CUSTOM_ESCAPE_SEQUENCE
SendRPC(ws, request, handle_notification_response)
def main(argv):
def on_message(ws, message):
response = api_pb2.Response()
response.ParseFromString(message)
if response.HasField('notification'):
handle_notification(response.notification)
else:
global callbacks
callback = callbacks[0]
del callbacks[0]
callback(response)
def on_error(ws, error):
print("Error: " + str(error))
def on_close(ws):
print("Connection closed")
def on_open(ws):
def list_sessions(ws):
def callback(response):
for window in response.list_sessions_response.windows:
for tab in window.tabs:
for session in tab.sessions:
subscribe_to_custom_escape_sequence(ws, session.uniqueIdentifier)
request = api_pb2.Request()
request.list_sessions_request.SetInParent()
SendRPC(ws, request, callback)
list_sessions(ws)
#websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://localhost:1912/",
on_message = on_message,
on_error = on_error,
on_close = on_close,
subprotocols = [ 'api.iterm2.com' ])
ws.on_open = on_open
ws.run_forever()
if __name__ == "__main__":
main(sys.argv)
Loading
Loading
@@ -7181,7 +7181,7 @@
DevelopmentTeam = H7V7XYVQ7D;
};
874206460564169600CFC3F1 = {
DevelopmentTeam = H7V7XYVQ7D;
DevelopmentTeam = CMC4EL7UVS;
ProvisioningStyle = Automatic;
};
A66717851DCE36C3000CE608 = {
Loading
Loading
@@ -8958,7 +8958,7 @@
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = YES;
DEVELOPMENT_TEAM = H7V7XYVQ7D;
DEVELOPMENT_TEAM = CMC4EL7UVS;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)",
Loading
Loading
@@ -10138,7 +10138,7 @@
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = H7V7XYVQ7D;
DEVELOPMENT_TEAM = CMC4EL7UVS;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)",
Loading
Loading
@@ -10207,7 +10207,7 @@
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = YES;
DEVELOPMENT_TEAM = H7V7XYVQ7D;
DEVELOPMENT_TEAM = CMC4EL7UVS;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)",
Loading
Loading
@@ -75,6 +75,7 @@ enum NotificationType {
NOTIFY_ON_SCREEN_UPDATE = 2;
NOTIFY_ON_PROMPT = 3;
NOTIFY_ON_LOCATION_CHANGE = 4;
NOTIFY_ON_CUSTOM_ESCAPE_SEQUENCE = 5;
}
 
message NotificationRequest {
Loading
Loading
@@ -104,6 +105,7 @@ message Notification {
optional ScreenUpdateNotification screen_update_notification = 2;
optional PromptNotification prompt_notification = 3;
optional LocationChangeNotification location_change_notification=4;
optional CustomEscapeSequenceNotification custom_escape_sequence_notification=5;
}
 
message KeystrokeNotification {
Loading
Loading
@@ -137,6 +139,13 @@ message LocationChangeNotification {
optional string session = 4;
}
 
// OSC 1337 ; Custom=id=<identity>:<payload> ST
message CustomEscapeSequenceNotification {
optional string session = 1;
optional string sender_identity = 2;
optional string payload = 3;
}
// Requests the contents of a range of lines.
message GetBufferRequest {
// Leave this empty to use the current session, if any.
Loading
Loading
@@ -481,7 +490,7 @@ message ListSessionsResponse {
repeated Session sessions = 1;
}
message Session {
optional string uniqueIdentifier = 1;
optional string uniqueIdentifier = 1;
}
repeated Window windows = 1;
}
Loading
Loading
@@ -458,6 +458,7 @@ static const NSUInteger kMaxHosts = 100;
NSMutableDictionary<id, ITMNotificationRequest *> *_updateSubscriptions;
NSMutableDictionary<id, ITMNotificationRequest *> *_promptSubscriptions;
NSMutableDictionary<id, ITMNotificationRequest *> *_locationChangeSubscriptions;
NSMutableDictionary<id, ITMNotificationRequest *> *_customEscapeSequenceNotifications;
 
// Used by auto-hide. We can't auto hide the tmux gateway session until at least one window has been opened.
BOOL _hideAfterTmuxWindowOpens;
Loading
Loading
@@ -544,6 +545,7 @@ static const NSUInteger kMaxHosts = 100;
_updateSubscriptions = [[NSMutableDictionary alloc] init];
_promptSubscriptions = [[NSMutableDictionary alloc] init];
_locationChangeSubscriptions = [[NSMutableDictionary alloc] init];
_customEscapeSequenceNotifications = [[NSMutableDictionary alloc] init];
 
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(coprocessChanged)
Loading
Loading
@@ -672,6 +674,8 @@ ITERM_WEAKLY_REFERENCEABLE
[_updateSubscriptions release];
[_promptSubscriptions release];
[_locationChangeSubscriptions release];
[_customEscapeSequenceNotifications release];
[_copyModeState release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
Loading
Loading
@@ -4367,6 +4371,7 @@ ITERM_WEAKLY_REFERENCEABLE
[_keystrokeSubscriptions removeObjectForKey:notification.object];
[_updateSubscriptions removeObjectForKey:notification.object];
[_locationChangeSubscriptions removeObjectForKey:notification.object];
[_customEscapeSequenceNotifications removeObjectForKey:notification.object];
}
 
- (void)applicationWillTerminate:(NSNotification *)notification {
Loading
Loading
@@ -8088,6 +8093,18 @@ ITERM_WEAKLY_REFERENCEABLE
}];
}
 
- (void)screenDidReceiveCustomEscapeSequenceWithParameters:(NSDictionary<NSString *, NSString *> *)parameters
payload:(NSString *)payload {
ITMNotification *notification = [[[ITMNotification alloc] init] autorelease];
notification.customEscapeSequenceNotification = [[[ITMCustomEscapeSequenceNotification alloc] init] autorelease];
notification.customEscapeSequenceNotification.session = self.guid;
notification.customEscapeSequenceNotification.senderIdentity = parameters[@"id"];
notification.customEscapeSequenceNotification.payload = payload;
[_customEscapeSequenceNotifications enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, ITMNotificationRequest * _Nonnull obj, BOOL * _Nonnull stop) {
[[[iTermApplication sharedApplication] delegate] postAPINotification:notification toConnection:key];
}];
}
- (BOOL)screenShouldSendReport {
return (_shell != nil) && (![self isTmuxClient]);
}
Loading
Loading
@@ -9136,6 +9153,9 @@ ITERM_WEAKLY_REFERENCEABLE
case ITMNotificationType_NotifyOnLocationChange:
subscriptions = _locationChangeSubscriptions;
break;
case ITMNotificationType_NotifyOnCustomEscapeSequence:
subscriptions = _customEscapeSequenceNotifications;
break;
}
if (!subscriptions) {
response.status = ITMNotificationResponse_Status_RequestMalformed;
Loading
Loading
Loading
Loading
@@ -4098,6 +4098,12 @@ static NSString *const kInilineFileInset = @"inset"; // NSValue of NSEdgeInsets
[delegate_ screenSetColor:color forKey:key];
}
 
- (void)terminalCustomEscapeSequenceWithParameters:(NSDictionary<NSString *, NSString *> *)parameters
payload:(NSString *)payload {
[delegate_ screenDidReceiveCustomEscapeSequenceWithParameters:parameters
payload:payload];
}
#pragma mark - Private
 
- (VT100GridCoordRange)commandRange {
Loading
Loading
Loading
Loading
@@ -222,6 +222,8 @@
 
- (void)screenCurrentHostDidChange:(VT100RemoteHost *)host;
- (void)screenCurrentDirectoryDidChangeTo:(NSString *)newPath;
- (void)screenDidReceiveCustomEscapeSequenceWithParameters:(NSDictionary<NSString *, NSString *> *)parameters
payload:(NSString *)payload;
 
// Ok to write to shell?
- (BOOL)screenShouldSendReport;
Loading
Loading
Loading
Loading
@@ -2284,6 +2284,28 @@ static const int kMaxScreenRows = 4096;
NSString *report = [NSString stringWithFormat:@"%c]1337;ReportVariable=%@%c", VT100CC_ESC, encodedValue ?: @"", VT100CC_BEL];
[delegate_ terminalSendReport:[report dataUsingEncoding:self.encoding]];
}
} else if ([key isEqualToString:@"Custom"]) {
if ([delegate_ terminalIsTrusted]) {
// Custom=key1=value1;key2=value2;...;keyN=valueN:payload
// ex:
// Custom=id=SenderIdentity:MessageGoesHere
NSInteger colon = [value rangeOfString:@":"].location;
if (colon != NSNotFound) {
NSArray<NSString *> *parts = [[value substringToIndex:colon] componentsSeparatedByString:@";"];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
[parts enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSInteger equals = [obj rangeOfString:@"="].location;
if (equals != NSNotFound) {
NSString *key = [obj substringToIndex:equals];
NSString *parameterValue = [obj substringFromIndex:equals + 1];
parameters[key] = parameterValue;
}
}];
NSString *payload = [value substringFromIndex:colon + 1];
[delegate_ terminalCustomEscapeSequenceWithParameters:parameters
payload:payload];
}
}
}
}
 
Loading
Loading
Loading
Loading
@@ -400,4 +400,8 @@ typedef NS_ENUM(NSUInteger, VT100AttentionRequestType) {
- (void)terminalWillEndLinkWithCode:(unsigned short)code;
- (void)terminalWillStartLinkWithCode:(unsigned short)code;
 
// Custom escape sequences
- (void)terminalCustomEscapeSequenceWithParameters:(NSDictionary<NSString *, NSString *> *)parameters
payload:(NSString *)payload;
@end
Loading
Loading
@@ -30,6 +30,7 @@ CF_EXTERN_C_BEGIN
@class ITMCodePointsPerCell;
@class ITMCoord;
@class ITMCoordRange;
@class ITMCustomEscapeSequenceNotification;
@class ITMGetBufferRequest;
@class ITMGetBufferResponse;
@class ITMGetPromptRequest;
Loading
Loading
@@ -67,6 +68,7 @@ typedef GPB_ENUM(ITMNotificationType) {
ITMNotificationType_NotifyOnScreenUpdate = 2,
ITMNotificationType_NotifyOnPrompt = 3,
ITMNotificationType_NotifyOnLocationChange = 4,
ITMNotificationType_NotifyOnCustomEscapeSequence = 5,
};
 
GPBEnumDescriptor *ITMNotificationType_EnumDescriptor(void);
Loading
Loading
@@ -484,6 +486,7 @@ typedef GPB_ENUM(ITMNotification_FieldNumber) {
ITMNotification_FieldNumber_ScreenUpdateNotification = 2,
ITMNotification_FieldNumber_PromptNotification = 3,
ITMNotification_FieldNumber_LocationChangeNotification = 4,
ITMNotification_FieldNumber_CustomEscapeSequenceNotification = 5,
};
 
@interface ITMNotification : GPBMessage
Loading
Loading
@@ -504,6 +507,10 @@ typedef GPB_ENUM(ITMNotification_FieldNumber) {
/** Test to see if @c locationChangeNotification has been set. */
@property(nonatomic, readwrite) BOOL hasLocationChangeNotification;
 
@property(nonatomic, readwrite, strong, null_resettable) ITMCustomEscapeSequenceNotification *customEscapeSequenceNotification;
/** Test to see if @c customEscapeSequenceNotification has been set. */
@property(nonatomic, readwrite) BOOL hasCustomEscapeSequenceNotification;
@end
 
#pragma mark - ITMKeystrokeNotification
Loading
Loading
@@ -597,6 +604,33 @@ typedef GPB_ENUM(ITMLocationChangeNotification_FieldNumber) {
 
@end
 
#pragma mark - ITMCustomEscapeSequenceNotification
typedef GPB_ENUM(ITMCustomEscapeSequenceNotification_FieldNumber) {
ITMCustomEscapeSequenceNotification_FieldNumber_Session = 1,
ITMCustomEscapeSequenceNotification_FieldNumber_SenderIdentity = 2,
ITMCustomEscapeSequenceNotification_FieldNumber_Payload = 3,
};
/**
* OSC 1337 ; Custom=id=<identity>:<payload> ST
**/
@interface ITMCustomEscapeSequenceNotification : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *session;
/** Test to see if @c session has been set. */
@property(nonatomic, readwrite) BOOL hasSession;
@property(nonatomic, readwrite, copy, null_resettable) NSString *senderIdentity;
/** Test to see if @c senderIdentity has been set. */
@property(nonatomic, readwrite) BOOL hasSenderIdentity;
@property(nonatomic, readwrite, copy, null_resettable) NSString *payload;
/** Test to see if @c payload has been set. */
@property(nonatomic, readwrite) BOOL hasPayload;
@end
#pragma mark - ITMGetBufferRequest
 
typedef GPB_ENUM(ITMGetBufferRequest_FieldNumber) {
Loading
Loading
Loading
Loading
@@ -50,12 +50,14 @@ GPBEnumDescriptor *ITMNotificationType_EnumDescriptor(void) {
if (!descriptor) {
static const char *valueNames =
"NotifyOnKeystroke\000NotifyOnScreenUpdate\000N"
"otifyOnPrompt\000NotifyOnLocationChange\000";
"otifyOnPrompt\000NotifyOnLocationChange\000Not"
"ifyOnCustomEscapeSequence\000";
static const int32_t values[] = {
ITMNotificationType_NotifyOnKeystroke,
ITMNotificationType_NotifyOnScreenUpdate,
ITMNotificationType_NotifyOnPrompt,
ITMNotificationType_NotifyOnLocationChange,
ITMNotificationType_NotifyOnCustomEscapeSequence,
};
GPBEnumDescriptor *worker =
[GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(ITMNotificationType)
Loading
Loading
@@ -76,6 +78,7 @@ BOOL ITMNotificationType_IsValidValue(int32_t value__) {
case ITMNotificationType_NotifyOnScreenUpdate:
case ITMNotificationType_NotifyOnPrompt:
case ITMNotificationType_NotifyOnLocationChange:
case ITMNotificationType_NotifyOnCustomEscapeSequence:
return YES;
default:
return NO;
Loading
Loading
@@ -721,6 +724,7 @@ BOOL ITMNotificationResponse_Status_IsValidValue(int32_t value__) {
@dynamic hasScreenUpdateNotification, screenUpdateNotification;
@dynamic hasPromptNotification, promptNotification;
@dynamic hasLocationChangeNotification, locationChangeNotification;
@dynamic hasCustomEscapeSequenceNotification, customEscapeSequenceNotification;
 
typedef struct ITMNotification__storage_ {
uint32_t _has_storage_[1];
Loading
Loading
@@ -728,6 +732,7 @@ typedef struct ITMNotification__storage_ {
ITMScreenUpdateNotification *screenUpdateNotification;
ITMPromptNotification *promptNotification;
ITMLocationChangeNotification *locationChangeNotification;
ITMCustomEscapeSequenceNotification *customEscapeSequenceNotification;
} ITMNotification__storage_;
 
// This method is threadsafe because it is initially called
Loading
Loading
@@ -772,6 +777,15 @@ typedef struct ITMNotification__storage_ {
.flags = GPBFieldOptional,
.dataType = GPBDataTypeMessage,
},
{
.name = "customEscapeSequenceNotification",
.dataTypeSpecific.className = GPBStringifySymbol(ITMCustomEscapeSequenceNotification),
.number = ITMNotification_FieldNumber_CustomEscapeSequenceNotification,
.hasIndex = 4,
.offset = (uint32_t)offsetof(ITMNotification__storage_, customEscapeSequenceNotification),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeMessage,
},
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:[ITMNotification class]
Loading
Loading
@@ -1086,6 +1100,71 @@ typedef struct ITMLocationChangeNotification__storage_ {
 
@end
 
#pragma mark - ITMCustomEscapeSequenceNotification
@implementation ITMCustomEscapeSequenceNotification
@dynamic hasSession, session;
@dynamic hasSenderIdentity, senderIdentity;
@dynamic hasPayload, payload;
typedef struct ITMCustomEscapeSequenceNotification__storage_ {
uint32_t _has_storage_[1];
NSString *session;
NSString *senderIdentity;
NSString *payload;
} ITMCustomEscapeSequenceNotification__storage_;
// This method is threadsafe because it is initially called
// in +initialize for each subclass.
+ (GPBDescriptor *)descriptor {
static GPBDescriptor *descriptor = nil;
if (!descriptor) {
static GPBMessageFieldDescription fields[] = {
{
.name = "session",
.dataTypeSpecific.className = NULL,
.number = ITMCustomEscapeSequenceNotification_FieldNumber_Session,
.hasIndex = 0,
.offset = (uint32_t)offsetof(ITMCustomEscapeSequenceNotification__storage_, session),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeString,
},
{
.name = "senderIdentity",
.dataTypeSpecific.className = NULL,
.number = ITMCustomEscapeSequenceNotification_FieldNumber_SenderIdentity,
.hasIndex = 1,
.offset = (uint32_t)offsetof(ITMCustomEscapeSequenceNotification__storage_, senderIdentity),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeString,
},
{
.name = "payload",
.dataTypeSpecific.className = NULL,
.number = ITMCustomEscapeSequenceNotification_FieldNumber_Payload,
.hasIndex = 2,
.offset = (uint32_t)offsetof(ITMCustomEscapeSequenceNotification__storage_, payload),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeString,
},
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:[ITMCustomEscapeSequenceNotification class]
rootClass:[ITMApiRoot class]
file:ITMApiRoot_FileDescriptor()
fields:fields
fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription))
storageSize:sizeof(ITMCustomEscapeSequenceNotification__storage_)
flags:GPBDescriptorInitializationFlag_None];
NSAssert(descriptor == nil, @"Startup recursed!");
descriptor = localDescriptor;
}
return descriptor;
}
@end
#pragma mark - ITMGetBufferRequest
 
@implementation ITMGetBufferRequest
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment