diff --git a/app/Base.lproj/Main.storyboard b/app/Base.lproj/Main.storyboard index c267a753..61f6d854 100644 --- a/app/Base.lproj/Main.storyboard +++ b/app/Base.lproj/Main.storyboard @@ -23,7 +23,7 @@ - + diff --git a/app/DelayedUITask.h b/app/DelayedUITask.h new file mode 100644 index 00000000..fd558f5e --- /dev/null +++ b/app/DelayedUITask.h @@ -0,0 +1,15 @@ +// +// DelayedUITask.h +// iSH +// +// Created by Theodore Dubois on 11/8/17. +// + +#import + +@interface DelayedUITask : NSObject + +- (instancetype)initWithTarget:(id)target action:(SEL)action; +- (void)schedule; + +@end diff --git a/app/DelayedUITask.m b/app/DelayedUITask.m new file mode 100644 index 00000000..378266ae --- /dev/null +++ b/app/DelayedUITask.m @@ -0,0 +1,35 @@ +// +// DelayedUITask.m +// iSH +// +// Created by Theodore Dubois on 11/8/17. +// + +#import "DelayedUITask.h" + +@interface DelayedUITask () + +@property id target; +@property SEL action; +@property NSTimer *timer; + +@end + +@implementation DelayedUITask + +- (instancetype)initWithTarget:(id)target action:(SEL)action { + if (self = [super init]) { + self.target = target; + self.action = action; + } + return self; +} + +- (void)schedule { + if (!self.timer.valid) { + self.timer = [NSTimer timerWithTimeInterval:1.0/60 target:self.target selector:self.action userInfo:nil repeats:NO]; + [NSRunLoop.mainRunLoop addTimer:self.timer forMode:NSDefaultRunLoopMode]; + } +} + +@end diff --git a/app/Info.plist b/app/Info.plist index 9213f39c..cd7187cb 100644 --- a/app/Info.plist +++ b/app/Info.plist @@ -28,6 +28,8 @@ armv7 + UIStatusBarHidden + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/app/Terminal.m b/app/Terminal.m index 140e02a1..325a3e6e 100644 --- a/app/Terminal.m +++ b/app/Terminal.m @@ -6,6 +6,7 @@ // #import "Terminal.h" +#import "DelayedUITask.h" #include "fs/tty.h" @interface Terminal () @@ -14,6 +15,9 @@ @property struct tty *tty; @property NSMutableData *pendingData; +@property DelayedUITask *refreshTask; +@property DelayedUITask *scrollToBottomTask; + @end @implementation Terminal @@ -25,6 +29,9 @@ static Terminal *terminal = nil; return terminal; if (self = [super init]) { self.pendingData = [NSMutableData new]; + self.refreshTask = [[DelayedUITask alloc] initWithTarget:self action:@selector(refresh)]; + self.scrollToBottomTask = [[DelayedUITask alloc] initWithTarget:self action:@selector(scrollToBottom)]; + WKWebViewConfiguration *config = [WKWebViewConfiguration new]; [config.userContentController addScriptMessageHandler:self name:@"log"]; self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config]; @@ -45,36 +52,40 @@ static Terminal *terminal = nil; - (size_t)write:(const void *)buf length:(size_t)len { @synchronized (self) { [self.pendingData appendData:[NSData dataWithBytes:buf length:len]]; - [self performSelectorOnMainThread:@selector(sendPendingOutput) withObject:nil waitUntilDone:NO]; + [self.refreshTask schedule]; } return len; } - (void)sendInput:(const char *)buf length:(size_t)len { tty_input(self.tty, buf, len); + [self.scrollToBottomTask schedule]; +} + +- (void)scrollToBottom { + [self.webView evaluateJavaScript:@"term.scrollToBottom()" completionHandler:nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == self.webView && [keyPath isEqualToString:@"loading"] && !self.webView.loading) { - [self sendPendingOutput]; + [self.refreshTask schedule]; [self.webView removeObserver:self forKeyPath:@"loading"]; } } -- (void)sendPendingOutput { +- (void)refresh { + if (self.webView.loading) + return; + NSString *str; @synchronized (self) { - if (self.webView.loading) - return; - if (self.pendingData.length == 0) - return; str = [[NSString alloc] initWithData:self.pendingData encoding:NSUTF8StringEncoding]; self.pendingData = [NSMutableData new]; } - NSError *err; + + NSError *err = nil; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@[str] options:0 error:&err]; - if (err != nil) - NSLog(@"%@", err); + NSAssert(err == nil, @"JSON serialization failed, wtf"); NSString *jsToEvaluate = [NSString stringWithFormat:@"term.write(%@[0])", [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]]; [self.webView evaluateJavaScript:jsToEvaluate completionHandler:nil]; } diff --git a/app/TerminalView.m b/app/TerminalView.m index c510a14c..b9367e87 100644 --- a/app/TerminalView.m +++ b/app/TerminalView.m @@ -21,6 +21,8 @@ WKWebView *webView = terminal.webView; [webView.configuration.userContentController addScriptMessageHandler:self name:@"focus"]; webView.frame = self.frame; + self.opaque = webView.opaque = NO; + webView.backgroundColor = UIColor.clearColor; [self addSubview:webView]; webView.translatesAutoresizingMaskIntoConstraints = NO; [webView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES; diff --git a/app/TerminalViewController.m b/app/TerminalViewController.m index b68b1380..87eadcbb 100644 --- a/app/TerminalViewController.m +++ b/app/TerminalViewController.m @@ -29,7 +29,7 @@ NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(keyboardDidSomething:) - name:UIKeyboardDidShowNotification + name:UIKeyboardWillShowNotification object:nil]; [center addObserver:self selector:@selector(keyboardDidSomething:) @@ -42,11 +42,33 @@ object:nil]; } +- (BOOL)prefersStatusBarHidden { + return YES; +} + - (void)keyboardDidSomething:(NSNotification *)notification { - NSValue *frame = notification.userInfo[UIKeyboardFrameEndUserInfoKey]; - self.bottomConstraint.constant = -frame.CGRectValue.size.height; - NSLog(@"bottom constant = %f", self.bottomConstraint.constant); - [self.termView setNeedsUpdateConstraints]; + if (self.termView.needsUpdateConstraints) { + // initial layout hasn't happened yet, so animation is going to look really bad + return; + } + + CGFloat pad = 0; + if ([notification.name isEqualToString:UIKeyboardWillShowNotification]) { + NSValue *frame = notification.userInfo[UIKeyboardFrameEndUserInfoKey]; + pad = frame.CGRectValue.size.height; + } + NSLog(@"pad = %f", pad); + self.bottomConstraint.constant = -pad; + [self.view setNeedsUpdateConstraints]; + NSNumber *interval = notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]; + NSNumber *curve = notification.userInfo[UIKeyboardAnimationCurveUserInfoKey]; + [UIView animateWithDuration:interval.doubleValue + delay:0 + options:curve.integerValue << 16 + animations:^{ + [self.view layoutIfNeeded]; + } + completion:nil]; } - (void)ishExited:(NSNotification *)notification { diff --git a/xtermjs/term.css b/xtermjs/term.css index 0b3e474f..c3615282 100644 --- a/xtermjs/term.css +++ b/xtermjs/term.css @@ -5,6 +5,10 @@ html, body, #terminal { margin: 0; } +body { + background: transparent; +} + #terminal { padding: 0 5px; }