Skip to content

Commit

Permalink
Make the iOS terminal look like a pty
Browse files Browse the repository at this point in the history
Also make the terminal and session owned by TerminalViewController.

This is in preparation for scene support.

Co-authored-by: Noah Peeters <noah.peeters@icloud.com>
  • Loading branch information
tbodt and NoahPeeters committed Oct 28, 2019
1 parent 32ae8bc commit 6c906ac
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 130 deletions.
58 changes: 4 additions & 54 deletions app/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@
#import "UserPreferences.h"
#include "kernel/init.h"
#include "kernel/calls.h"
#include "fs/path.h"
#include "fs/dyndev.h"
#include "fs/devices.h"
#include "fs/path.h"

@interface AppDelegate ()

@property int sessionPid;

@property BOOL exiting;

@end
Expand Down Expand Up @@ -158,73 +156,32 @@ - (int)startThings {
NSString *sockTmp = [NSTemporaryDirectory() stringByAppendingString:@"ishsock"];
sock_tmp_prefix = strdup(sockTmp.UTF8String);

tty_drivers[TTY_CONSOLE_MAJOR] = &ios_tty_driver;
tty_drivers[TTY_CONSOLE_MAJOR] = &ios_console_driver;
set_console_device(TTY_CONSOLE_MAJOR, 1);
err = create_stdio("/dev/console");
err = create_stdio("/dev/console", TTY_CONSOLE_MAJOR, 1);
if (err < 0)
return err;

NSArray<NSString *> *command;
command = UserPreferences.shared.bootCommand;
NSLog(@"%@", command);
char argv[4096];
[self convertCommand:command toArgs:argv limitSize:sizeof(argv)];
[Terminal convertCommand:command toArgs:argv limitSize:sizeof(argv)];
const char *envp = "TERM=xterm-256color\0";
err = sys_execve(argv, argv, envp);
if (err < 0)
return err;
task_start(current);

err = [self startSession];
if (err < 0)
return err;

return 0;
}

- (int)startSession {
int err = become_new_init_child();
if (err < 0)
return err;
err = create_stdio("/dev/tty7");
if (err < 0)
return err;
char argv[4096];
[self convertCommand:UserPreferences.shared.launchCommand toArgs:argv limitSize:sizeof(argv)];
const char *envp = "TERM=xterm-256color\0";
err = sys_execve(argv, argv, envp);
if (err < 0)
return err;
self.sessionPid = current->pid;
task_start(current);
return 0;
}

- (void)convertCommand:(NSArray<NSString *> *)command toArgs:(char *)argv limitSize:(size_t)maxSize {
char *p = argv;
for (NSString *cmd in command) {
const char *c = cmd.UTF8String;
// Save space for the final NUL byte in argv
while (p < argv + maxSize - 1 && (*p++ = *c++));
// If we reach the end of the buffer, the last string still needs to be
// NUL terminated
*p = '\0';
}
// Add the final NUL byte to argv
*++p = '\0';
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// get the network permissions popup to appear on chinese devices
[[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:@"http://captive.apple.com"]] resume];

[UserPreferences.shared addObserver:self forKeyPath:@"shouldDisableDimming" options:NSKeyValueObservingOptionInitial context:nil];

[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(processExited:)
name:ProcessExitedNotification
object:nil];

int err = [self startThings];
if (err < 0) {
NSString *message = [NSString stringWithFormat:@"could not initialize"];
Expand All @@ -241,13 +198,6 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
UIApplication.sharedApplication.idleTimerDisabled = UserPreferences.shared.shouldDisableDimming;
}

- (void)processExited:(NSNotification *)notif {
int pid = [notif.userInfo[@"pid"] intValue];
if (pid == self.sessionPid) {
[self startSession];
}
}

- (void)showMessage:(NSString *)message subtitle:(NSString *)subtitle fatal:(BOOL)fatal {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:message message:subtitle preferredStyle:UIAlertControllerStyleAlert];
Expand Down
11 changes: 10 additions & 1 deletion app/Terminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,27 @@
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>

struct tty;

@interface Terminal : NSObject

+ (Terminal *)terminalWithType:(int)type number:(int)number;
// Returns a strong struct tty and a Terminal that has a weak reference to the same tty
+ (Terminal *)createPseudoTerminal:(struct tty **)tty;

+ (void)convertCommand:(NSArray<NSString *> *)command toArgs:(char *)argv limitSize:(size_t)maxSize;

- (int)write:(const void *)buf length:(size_t)len;
- (void)sendInput:(const char *)buf length:(size_t)len;

- (NSString *)arrow:(char)direction;

// Make this terminal no longer be the singleton terminal with its type and number. Will happen eventually if all references go away, but sometimes you want it to happen now.
- (void)destroy;

@property (readonly) WKWebView *webView;
@property (nonatomic) BOOL enableVoiceOverAnnounce;

@end

extern struct tty_driver ios_tty_driver;
extern struct tty_driver ios_console_driver;
75 changes: 45 additions & 30 deletions app/Terminal.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
#import "Terminal.h"
#import "DelayedUITask.h"
#import "UserPreferences.h"
#include "fs/devices.h"
#include "fs/tty.h"

extern struct tty_driver ios_pty_driver;

@interface Terminal () <WKScriptMessageHandler>

@property WKWebView *webView;
Expand All @@ -22,6 +25,8 @@ @interface Terminal () <WKScriptMessageHandler>

@property BOOL applicationCursor;

@property NSNumber *terminalsKey;

@end

@interface CustomWebView : WKWebView
Expand All @@ -34,11 +39,11 @@ - (BOOL)becomeFirstResponder {

@implementation Terminal

static NSMutableDictionary<NSNumber *, Terminal *> *terminals;
static NSMapTable<NSNumber *, Terminal *> *terminals;

- (instancetype)initWithType:(int)type number:(int)num {
NSNumber *key = @(dev_make(type, num));
Terminal *terminal = [terminals objectForKey:key];
self.terminalsKey = @(dev_make(type, num));
Terminal *terminal = [terminals objectForKey:self.terminalsKey];
if (terminal)
return terminal;

Expand All @@ -60,14 +65,23 @@ - (instancetype)initWithType:(int)type number:(int)num {
[self.webView loadFileURL:xtermHtmlFile allowingReadAccessToURL:xtermHtmlFile];
[self _addPreferenceObservers];

[terminals setObject:self forKey:key];
[terminals setObject:self forKey:self.terminalsKey];
}
return self;
}

+ (Terminal *)createPseudoTerminal:(struct tty **)tty {
*tty = pty_open_fake(&ios_pty_driver);
if (IS_ERR(*tty))
return nil;
return (__bridge Terminal *) (*tty)->data;
}

- (void)setTty:(struct tty *)tty {
_tty = tty;
[self syncWindowSize];
dispatch_async(dispatch_get_main_queue(), ^{
[self syncWindowSize];
});
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
Expand Down Expand Up @@ -115,6 +129,8 @@ - (int)write:(const void *)buf length:(size_t)len {
}

- (void)sendInput:(const char *)buf length:(size_t)len {
if (self.tty == NULL)
return;
tty_input(self.tty, buf, len, 0);
[self.webView evaluateJavaScript:@"exports.setUserGesture()" completionHandler:nil];
[self.scrollToBottomTask schedule];
Expand Down Expand Up @@ -240,18 +256,35 @@ - (void)refresh {
[self.webView evaluateJavaScript:jsToEvaluate completionHandler:nil];
}

+ (void)convertCommand:(NSArray<NSString *> *)command toArgs:(char *)argv limitSize:(size_t)maxSize {
char *p = argv;
for (NSString *cmd in command) {
const char *c = cmd.UTF8String;
// Save space for the final NUL byte in argv
while (p < argv + maxSize - 1 && (*p++ = *c++));
// If we reach the end of the buffer, the last string still needs to be
// NUL terminated
*p = '\0';
}
// Add the final NUL byte to argv
*++p = '\0';
}

+ (Terminal *)terminalWithType:(int)type number:(int)number {
return [[Terminal alloc] initWithType:type number:number];
}

- (void)destroy {
[terminals removeObjectForKey:self.terminalsKey];
}

+ (void)initialize {
terminals = [NSMutableDictionary new];
terminals = [NSMapTable strongToWeakObjectsMapTable];
}

@end

static int ios_tty_init(struct tty *tty) {
tty->refcount++;
void (^init_block)(void) = ^{
Terminal *terminal = [Terminal terminalWithType:tty->type number:tty->num];
tty->data = (void *) CFBridgingRetain(terminal);
Expand All @@ -262,27 +295,6 @@ static int ios_tty_init(struct tty *tty) {
else
dispatch_sync(dispatch_get_main_queue(), init_block);

// termios
tty->termios.lflags = ISIG_ | ICANON_ | ECHO_ | ECHOE_ | ECHOCTL_;
tty->termios.iflags = ICRNL_;
tty->termios.oflags = OPOST_ | ONLCR_;
tty->termios.cc[VINTR_] = '\x03';
tty->termios.cc[VQUIT_] = '\x1c';
tty->termios.cc[VERASE_] = '\x7f';
tty->termios.cc[VKILL_] = '\x15';
tty->termios.cc[VEOF_] = '\x04';
tty->termios.cc[VTIME_] = 0;
tty->termios.cc[VMIN_] = 1;
tty->termios.cc[VSTART_] = '\x11';
tty->termios.cc[VSTOP_] = '\x13';
tty->termios.cc[VSUSP_] = '\x1a';
tty->termios.cc[VEOL_] = 0;
tty->termios.cc[VREPRINT_] = '\x12';
tty->termios.cc[VDISCARD_] = '\x0f';
tty->termios.cc[VWERASE_] = '\x17';
tty->termios.cc[VLNEXT_] = '\x16';
tty->termios.cc[VEOL2_] = 0;

return 0;
}

Expand All @@ -292,12 +304,15 @@ static int ios_tty_write(struct tty *tty, const void *buf, size_t len, bool bloc
}

static void ios_tty_cleanup(struct tty *tty) {
CFBridgingRelease(tty->data);
Terminal *terminal = CFBridgingRelease(tty->data);
tty->data = NULL;
terminal.tty = NULL;
}

struct tty_driver_ops ios_tty_ops = {
.init = ios_tty_init,
.write = ios_tty_write,
.cleanup = ios_tty_cleanup,
};
DEFINE_TTY_DRIVER(ios_tty_driver, &ios_tty_ops, 64);
DEFINE_TTY_DRIVER(ios_console_driver, &ios_tty_ops, 64);
struct tty_driver ios_pty_driver = {.ops = &ios_tty_ops};
62 changes: 60 additions & 2 deletions app/TerminalViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#import "ArrowBarButton.h"
#import "UserPreferences.h"
#import "AboutViewController.h"
#include "kernel/init.h"
#include "kernel/task.h"
#include "kernel/calls.h"
#include "fs/devices.h"

@interface TerminalViewController () <UIGestureRecognizerDelegate>
Expand All @@ -38,13 +41,22 @@ @interface TerminalViewController () <UIGestureRecognizerDelegate>
@property (weak, nonatomic) IBOutlet UIButton *pasteButton;
@property (weak, nonatomic) IBOutlet UIButton *hideKeyboardButton;

@property int sessionPid;
@property (nonatomic) Terminal *sessionTerminal;

@end

@implementation TerminalViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.terminal = [Terminal terminalWithType:TTY_CONSOLE_MAJOR number:7];

[self startSession];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(processExited:)
name:ProcessExitedNotification
object:nil];

[self.termView becomeFirstResponder];

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
Expand Down Expand Up @@ -85,6 +97,43 @@ - (void)viewDidLoad {
}
}

- (int)startSession {
int err = become_new_init_child();
if (err < 0)
return err;
struct tty *tty;
[self.sessionTerminal destroy];
self.sessionTerminal = nil;
Terminal *terminal = [Terminal createPseudoTerminal:&tty];
if (terminal == nil) {
NSAssert(IS_ERR(tty), @"tty should be error");
return (int) PTR_ERR(tty);
}
self.sessionTerminal = terminal;
NSString *stdioFile = [NSString stringWithFormat:@"/dev/pts/%d", tty->num];
err = create_stdio(stdioFile.fileSystemRepresentation, TTY_PSEUDO_SLAVE_MAJOR, tty->num);
if (err < 0)
return err;
tty_release(tty);

char argv[4096];
[Terminal convertCommand:UserPreferences.shared.launchCommand toArgs:argv limitSize:sizeof(argv)];
const char *envp = "TERM=xterm-256color\0";
err = sys_execve(argv, argv, envp);
if (err < 0)
return err;
self.sessionPid = current->pid;
task_start(current);
return 0;
}

- (void)processExited:(NSNotification *)notif {
int pid = [notif.userInfo[@"pid"] intValue];
if (pid == self.sessionPid) {
[self startSession];
}
}

- (void)dealloc {
@try {
[[UserPreferences shared] removeObserver:self forKeyPath:@"theme"];
Expand Down Expand Up @@ -238,7 +287,10 @@ - (IBAction)pressArrow:(ArrowBarButton *)sender {

- (void)switchTerminal:(UIKeyCommand *)sender {
unsigned i = (unsigned) sender.input.integerValue;
self.terminal = [Terminal terminalWithType:TTY_CONSOLE_MAJOR number:i];
if (i == 7)
self.terminal = self.sessionTerminal;
else
self.terminal = [Terminal terminalWithType:TTY_CONSOLE_MAJOR number:i];
}

- (NSArray<UIKeyCommand *> *)keyCommands {
Expand All @@ -260,6 +312,12 @@ - (void)setTerminal:(Terminal *)terminal {
self.termView.terminal = self.terminal;
}

- (void)setSessionTerminal:(Terminal *)sessionTerminal {
if (_terminal == _sessionTerminal)
self.terminal = sessionTerminal;
_sessionTerminal = sessionTerminal;
}

@end

@interface BarView : UIInputView
Expand Down
Loading

0 comments on commit 6c906ac

Please sign in to comment.