aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/devdraw/mac-screen.m
diff options
context:
space:
mode:
authorRuss Cox <rsc@swtch.com>2020-01-08 20:07:15 -0500
committerRuss Cox <rsc@swtch.com>2020-01-13 16:46:14 -0500
commit933b98054f40bb224acda134d7bb77a023bcc57f (patch)
tree9d8994836d92c20b52b126970347240c08de8ef6 /src/cmd/devdraw/mac-screen.m
parentce27d7babdf2ee09ff6d1f8d4a166c2208995774 (diff)
downloadplan9port-933b98054f40bb224acda134d7bb77a023bcc57f.tar.gz
plan9port-933b98054f40bb224acda134d7bb77a023bcc57f.tar.bz2
plan9port-933b98054f40bb224acda134d7bb77a023bcc57f.zip
devdraw: use consistent mac-* prefix on macOS files
We were using osx- and cocoa- but it's not even OS X anymore.
Diffstat (limited to 'src/cmd/devdraw/mac-screen.m')
-rw-r--r--src/cmd/devdraw/mac-screen.m1248
1 files changed, 1248 insertions, 0 deletions
diff --git a/src/cmd/devdraw/mac-screen.m b/src/cmd/devdraw/mac-screen.m
new file mode 100644
index 00000000..c115f867
--- /dev/null
+++ b/src/cmd/devdraw/mac-screen.m
@@ -0,0 +1,1248 @@
+#define Cursor OSXCursor
+#define Point OSXPoint
+#define Rect OSXRect
+
+#import <Cocoa/Cocoa.h>
+#import <Metal/Metal.h>
+#import <QuartzCore/CAMetalLayer.h>
+
+#undef Cursor
+#undef Point
+#undef Rect
+
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include "mac-screen.h"
+#include "devdraw.h"
+#include "bigarrow.h"
+#include "glendapng.h"
+
+AUTOFRAMEWORK(Cocoa)
+AUTOFRAMEWORK(Metal)
+AUTOFRAMEWORK(QuartzCore)
+
+#define LOG if(0)NSLog
+
+static void setprocname(const char*);
+static uint keycvt(uint);
+static uint msec(void);
+static Memimage* initimg(void);
+
+void
+usage(void)
+{
+ fprint(2, "usage: devdraw (don't run directly)\n");
+ threadexitsall("usage");
+}
+
+
+@interface AppDelegate : NSObject<NSApplicationDelegate,NSWindowDelegate>
++ (void)makewin:(NSValue *)v;
++ (void)callkicklabel:(NSString *)v;
++ (void)callsetNeedsDisplayInRect:(NSValue *)v;
++ (void)callsetcursor:(NSValue *)v;
+@end
+@interface DevDrawView : NSView<NSTextInputClient>
+- (void)clearInput;
+- (void)getmouse:(NSEvent *)e;
+- (void)sendmouse:(NSUInteger)b;
+- (void)resetLastInputRect;
+- (void)enlargeLastInputRect:(NSRect)r;
+@end
+@interface DrawLayer : CAMetalLayer
+@end
+
+static AppDelegate *myApp = NULL;
+static DevDrawView *myContent = NULL;
+static NSWindow *win = NULL;
+static NSCursor *currentCursor = NULL;
+
+static DrawLayer *layer;
+static id<MTLDevice> device;
+static id<MTLCommandQueue> commandQueue;
+static id<MTLTexture> texture;
+
+static Memimage *img = NULL;
+
+static QLock snarfl;
+
+void
+threadmain(int argc, char **argv)
+{
+ /*
+ * Move the protocol off stdin/stdout so that
+ * any inadvertent prints don't screw things up.
+ */
+ dup(0,3);
+ dup(1,4);
+ close(0);
+ close(1);
+ open("/dev/null", OREAD);
+ open("/dev/null", OWRITE);
+
+ ARGBEGIN{
+ case 'D': /* for good ps -a listings */
+ break;
+ case 'f': /* fall through for backward compatibility */
+ case 'g':
+ case 'b':
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ setprocname(argv0);
+
+ @autoreleasepool{
+ [NSApplication sharedApplication];
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+ myApp = [AppDelegate new];
+ [NSApp setDelegate:myApp];
+ [NSApp run];
+ }
+}
+
+
+void
+callservep9p(void *v)
+{
+ USED(v);
+
+ servep9p();
+ [NSApp terminate:myApp];
+}
+
+@implementation AppDelegate
+
++ (void)makewin:(NSValue *)v
+{
+ NSRect r, sr;
+ Rectangle wr;
+ int set;
+ char *s;
+ NSArray *allDevices;
+
+ const NSWindowStyleMask Winstyle = NSWindowStyleMaskTitled
+ | NSWindowStyleMaskClosable
+ | NSWindowStyleMaskMiniaturizable
+ | NSWindowStyleMaskResizable;
+
+ sr = [[NSScreen mainScreen] frame];
+ r = [[NSScreen mainScreen] visibleFrame];
+
+ s = [v pointerValue];
+ LOG(@"makewin(%s)", s);
+ if(s && *s){
+ if(parsewinsize(s, &wr, &set) < 0)
+ sysfatal("%r");
+ }else{
+ wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
+ set = 0;
+ }
+
+ r.origin.x = wr.min.x;
+ r.origin.y = sr.size.height-wr.max.y; /* winsize is top-left-based */
+ r.size.width = fmin(Dx(wr), r.size.width);
+ r.size.height = fmin(Dy(wr), r.size.height);
+ r = [NSWindow contentRectForFrameRect:r styleMask:Winstyle];
+
+ win = [[NSWindow alloc]
+ initWithContentRect:r
+ styleMask:Winstyle
+ backing:NSBackingStoreBuffered defer:NO];
+ [win setTitle:@"devdraw"];
+
+ if(!set)
+ [win center];
+ [win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+ [win setContentMinSize:NSMakeSize(64,64)];
+ [win setOpaque:YES];
+ [win setRestorable:NO];
+ [win setAcceptsMouseMovedEvents:YES];
+ [win setDelegate:myApp];
+
+ myContent = [DevDrawView new];
+ [win setContentView:myContent];
+ [myContent setWantsLayer:YES];
+ [myContent setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
+
+ device = nil;
+ allDevices = MTLCopyAllDevices();
+ for(id mtlDevice in allDevices) {
+ if ([mtlDevice isLowPower] && ![mtlDevice isRemovable]) {
+ device = mtlDevice;
+ break;
+ }
+ }
+ if(!device)
+ device = MTLCreateSystemDefaultDevice();
+
+ commandQueue = [device newCommandQueue];
+
+ layer = (DrawLayer *)[myContent layer];
+ layer.device = device;
+ layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
+ layer.framebufferOnly = YES;
+ layer.opaque = YES;
+
+ // We use a default transparent layer on top of the CAMetalLayer.
+ // This seems to make fullscreen applications behave.
+ {
+ CALayer *stub = [CALayer layer];
+ stub.frame = CGRectMake(0, 0, 1, 1);
+ [stub setNeedsDisplay];
+ [layer addSublayer:stub];
+ }
+
+ [NSEvent setMouseCoalescingEnabled:NO];
+
+ topwin();
+}
+
++ (void)callkicklabel:(NSString *)s
+{
+ LOG(@"callkicklabel(%@)", s);
+ [win setTitle:s];
+ [[NSApp dockTile] setBadgeLabel:s];
+}
+
+
++ (void)callsetNeedsDisplayInRect:(NSValue *)v
+{
+ NSRect r;
+ dispatch_time_t time;
+
+ r = [v rectValue];
+ LOG(@"callsetNeedsDisplayInRect(%g, %g, %g, %g)", r.origin.x, r.origin.y, r.size.width, r.size.height);
+ r = [win convertRectFromBacking:r];
+ LOG(@"setNeedsDisplayInRect(%g, %g, %g, %g)", r.origin.x, r.origin.y, r.size.width, r.size.height);
+ [layer setNeedsDisplayInRect:r];
+
+ time = dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC);
+ dispatch_after(time, dispatch_get_main_queue(), ^(void){
+ [layer setNeedsDisplayInRect:r];
+ });
+
+ [myContent enlargeLastInputRect:r];
+}
+
+typedef struct Cursors Cursors;
+struct Cursors {
+ Cursor *c;
+ Cursor2 *c2;
+};
+
++ (void)callsetcursor:(NSValue *)v
+{
+ Cursors *cs;
+ Cursor *c;
+ Cursor2 *c2;
+ NSBitmapImageRep *r, *r2;
+ NSImage *i;
+ NSPoint p;
+ uchar *plane[5], *plane2[5];
+ uint b;
+
+ cs = [v pointerValue];
+ c = cs->c;
+ if(!c)
+ c = &bigarrow;
+ c2 = cs->c2;
+ if(!c2)
+ c2 = &bigarrow2;
+
+ r = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nil
+ pixelsWide:16
+ pixelsHigh:16
+ bitsPerSample:1
+ samplesPerPixel:2
+ hasAlpha:YES
+ isPlanar:YES
+ colorSpaceName:NSDeviceWhiteColorSpace
+ bytesPerRow:2
+ bitsPerPixel:0];
+ [r getBitmapDataPlanes:plane];
+ for(b=0; b<nelem(c->set); b++){
+ plane[0][b] = ~c->set[b] & c->clr[b];
+ plane[1][b] = c->set[b] | c->clr[b];
+ }
+
+ r2 = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nil
+ pixelsWide:32
+ pixelsHigh:32
+ bitsPerSample:1
+ samplesPerPixel:2
+ hasAlpha:YES
+ isPlanar:YES
+ colorSpaceName:NSDeviceWhiteColorSpace
+ bytesPerRow:4
+ bitsPerPixel:0];
+ [r2 getBitmapDataPlanes:plane2];
+ for(b=0; b<nelem(c2->set); b++){
+ plane2[0][b] = ~c2->set[b] & c2->clr[b];
+ plane2[1][b] = c2->set[b] | c2->clr[b];
+ }
+
+ // For checking out the cursor bitmap image
+/*
+ static BOOL saveimg = YES;
+ if(saveimg){
+ NSData *data = [r representationUsingType: NSBitmapImageFileTypeBMP properties: @{}];
+ [data writeToFile: @"/tmp/r.bmp" atomically: NO];
+ data = [r2 representationUsingType: NSBitmapImageFileTypeBMP properties: @{}];
+ [data writeToFile: @"/tmp/r2.bmp" atomically: NO];
+ saveimg = NO;
+ }
+*/
+
+ i = [[NSImage alloc] initWithSize:NSMakeSize(16, 16)];
+ [i addRepresentation:r2];
+ [i addRepresentation:r];
+
+ p = NSMakePoint(-c->offset.x, -c->offset.y);
+ currentCursor = [[NSCursor alloc] initWithImage:i hotSpot:p];
+
+ [win invalidateCursorRectsForView:myContent];
+}
+
+- (void)applicationDidFinishLaunching:(id)arg
+{
+ NSMenu *m, *sm;
+ NSData *d;
+ NSImage *i;
+
+ LOG(@"applicationDidFinishLaunching");
+
+ sm = [NSMenu new];
+ [sm addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
+ [sm addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"];
+ [sm addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
+ m = [NSMenu new];
+ [m addItemWithTitle:@"DEVDRAW" action:NULL keyEquivalent:@""];
+ [m setSubmenu:sm forItem:[m itemWithTitle:@"DEVDRAW"]];
+ [NSApp setMainMenu:m];
+
+ d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)];
+ i = [[NSImage alloc] initWithData:d];
+ [NSApp setApplicationIconImage:i];
+ [[NSApp dockTile] display];
+
+ proccreate(callservep9p, nil, 0);
+}
+
+- (NSApplicationPresentationOptions)window:(id)arg
+ willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
+ NSApplicationPresentationOptions o;
+ o = proposedOptions;
+ o &= ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar);
+ o |= NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
+ return o;
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
+ return YES;
+}
+
+- (void)windowDidResize:(NSNotification *)notification
+{
+ if(![myContent inLiveResize] && img) {
+ resizeimg();
+ }
+}
+
+- (void)windowDidBecomeKey:(id)arg
+{
+ [myContent sendmouse:0];
+}
+
+@end
+
+@implementation DevDrawView
+{
+ NSMutableString *_tmpText;
+ NSRange _markedRange;
+ NSRange _selectedRange;
+ NSRect _lastInputRect; // The view is flipped, this is not.
+ BOOL _tapping;
+ NSUInteger _tapFingers;
+ NSUInteger _tapTime;
+}
+
+- (id)init
+{
+ LOG(@"View init");
+ self = [super init];
+ [self setAllowedTouchTypes:NSTouchTypeMaskDirect|NSTouchTypeMaskIndirect];
+ _tmpText = [[NSMutableString alloc] initWithCapacity:2];
+ _markedRange = NSMakeRange(NSNotFound, 0);
+ _selectedRange = NSMakeRange(0, 0);
+ return self;
+}
+
+- (CALayer *)makeBackingLayer
+{
+ LOG(@"makeBackingLayer");
+ return [DrawLayer layer];
+}
+
+- (BOOL)wantsUpdateLayer
+{
+ return YES;
+}
+
+- (BOOL)isOpaque
+{
+ return YES;
+}
+
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (BOOL)acceptsFirstResponder
+{
+ return YES;
+}
+
+- (void)mouseMoved:(NSEvent*)e{ [self getmouse:e];}
+- (void)mouseDown:(NSEvent*)e{ [self getmouse:e];}
+- (void)mouseDragged:(NSEvent*)e{ [self getmouse:e];}
+- (void)mouseUp:(NSEvent*)e{ [self getmouse:e];}
+- (void)otherMouseDown:(NSEvent*)e{ [self getmouse:e];}
+- (void)otherMouseDragged:(NSEvent*)e{ [self getmouse:e];}
+- (void)otherMouseUp:(NSEvent*)e{ [self getmouse:e];}
+- (void)rightMouseDown:(NSEvent*)e{ [self getmouse:e];}
+- (void)rightMouseDragged:(NSEvent*)e{ [self getmouse:e];}
+- (void)rightMouseUp:(NSEvent*)e{ [self getmouse:e];}
+
+- (void)scrollWheel:(NSEvent*)e
+{
+ NSInteger s;
+
+ s = [e scrollingDeltaY];
+ if(s > 0)
+ [self sendmouse:8];
+ else if (s < 0)
+ [self sendmouse:16];
+}
+
+- (void)keyDown:(NSEvent*)e
+{
+ LOG(@"keyDown to interpret");
+
+ [self interpretKeyEvents:[NSArray arrayWithObject:e]];
+
+ [self resetLastInputRect];
+}
+
+- (void)flagsChanged:(NSEvent*)e
+{
+ static NSEventModifierFlags omod;
+ NSEventModifierFlags m;
+ uint b;
+
+ LOG(@"flagsChanged");
+ m = [e modifierFlags];
+
+ b = [NSEvent pressedMouseButtons];
+ b = (b&~6) | (b&4)>>1 | (b&2)<<1;
+ if(b){
+ if(m & ~omod & NSEventModifierFlagControl)
+ b |= 1;
+ if(m & ~omod & NSEventModifierFlagOption)
+ b |= 2;
+ if(m & ~omod & NSEventModifierFlagCommand)
+ b |= 4;
+ [self sendmouse:b];
+ }else if(m & ~omod & NSEventModifierFlagOption)
+ keystroke(Kalt);
+
+ omod = m;
+}
+
+- (void)magnifyWithEvent:(NSEvent*)e
+{
+ if(fabs([e magnification]) > 0.02)
+ [[self window] toggleFullScreen:nil];
+}
+
+- (void)touchesBeganWithEvent:(NSEvent*)e
+{
+ _tapping = YES;
+ _tapFingers = [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count;
+ _tapTime = msec();
+}
+- (void)touchesMovedWithEvent:(NSEvent*)e
+{
+ _tapping = NO;
+}
+- (void)touchesEndedWithEvent:(NSEvent*)e
+{
+ if(_tapping
+ && [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count == 0
+ && msec() - _tapTime < 250){
+ switch(_tapFingers){
+ case 3:
+ [self sendmouse:2];
+ [self sendmouse:0];
+ break;
+ case 4:
+ [self sendmouse:2];
+ [self sendmouse:1];
+ [self sendmouse:0];
+ break;
+ }
+ _tapping = NO;
+ }
+}
+- (void)touchesCancelledWithEvent:(NSEvent*)e
+{
+ _tapping = NO;
+}
+
+- (void)getmouse:(NSEvent *)e
+{
+ NSUInteger b;
+ NSEventModifierFlags m;
+
+ b = [NSEvent pressedMouseButtons];
+ b = b&~6 | (b&4)>>1 | (b&2)<<1;
+ b = mouseswap(b);
+
+ if(b == 1){
+ m = [e modifierFlags];
+ if(m & NSEventModifierFlagOption){
+ abortcompose();
+ b = 2;
+ }else
+ if(m & NSEventModifierFlagCommand)
+ b = 4;
+ }
+ [self sendmouse:b];
+}
+
+- (void)sendmouse:(NSUInteger)b
+{
+ NSPoint p;
+
+ p = [self.window convertPointToBacking:
+ [self.window mouseLocationOutsideOfEventStream]];
+ p.y = Dy(mouserect) - p.y;
+ // LOG(@"(%g, %g) <- sendmouse(%d)", p.x, p.y, (uint)b);
+ mousetrack(p.x, p.y, b, msec());
+ if(b && _lastInputRect.size.width && _lastInputRect.size.height)
+ [self resetLastInputRect];
+}
+
+- (void)resetCursorRects {
+ [super resetCursorRects];
+ [self addCursorRect:self.bounds cursor:currentCursor];
+}
+
+- (void)viewDidEndLiveResize
+{
+ [super viewDidEndLiveResize];
+ if(img)
+ resizeimg();
+}
+
+- (void)viewDidChangeBackingProperties
+{
+ [super viewDidChangeBackingProperties];
+ if(img)
+ resizeimg();
+}
+
+// conforms to protocol NSTextInputClient
+- (BOOL)hasMarkedText
+{
+ LOG(@"hasMarkedText");
+ return _markedRange.location != NSNotFound;
+}
+- (NSRange)markedRange
+{
+ LOG(@"markedRange");
+ return _markedRange;
+}
+- (NSRange)selectedRange
+{
+ LOG(@"selectedRange");
+ return _selectedRange;
+}
+- (void)setMarkedText:(id)string
+ selectedRange:(NSRange)sRange
+ replacementRange:(NSRange)rRange
+{
+ NSString *str;
+
+ LOG(@"setMarkedText: %@ (%ld, %ld) (%ld, %ld)", string,
+ sRange.location, sRange.length,
+ rRange.location, rRange.length);
+
+ [self clearInput];
+
+ if([string isKindOfClass:[NSAttributedString class]])
+ str = [string string];
+ else
+ str = string;
+
+ if(rRange.location == NSNotFound){
+ if(_markedRange.location != NSNotFound){
+ rRange = _markedRange;
+ }else{
+ rRange = _selectedRange;
+ }
+ }
+
+ if(str.length == 0){
+ [_tmpText deleteCharactersInRange:rRange];
+ [self unmarkText];
+ }else{
+ _markedRange = NSMakeRange(rRange.location, str.length);
+ [_tmpText replaceCharactersInRange:rRange withString:str];
+ }
+ _selectedRange.location = rRange.location + sRange.location;
+ _selectedRange.length = sRange.length;
+
+ if(_tmpText.length){
+ uint i;
+ LOG(@"text length %ld", _tmpText.length);
+ for(i = 0; i <= _tmpText.length; ++i){
+ if(i == _markedRange.location)
+ keystroke('[');
+ if(_selectedRange.length){
+ if(i == _selectedRange.location)
+ keystroke('{');
+ if(i == NSMaxRange(_selectedRange))
+ keystroke('}');
+ }
+ if(i == NSMaxRange(_markedRange))
+ keystroke(']');
+ if(i < _tmpText.length)
+ keystroke([_tmpText characterAtIndex:i]);
+ }
+ int l;
+ l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
+ + (_selectedRange.length > 0);
+ LOG(@"move left %d", l);
+ for(i = 0; i < l; ++i)
+ keystroke(Kleft);
+ }
+
+ LOG(@"text: \"%@\" (%ld,%ld) (%ld,%ld)", _tmpText,
+ _markedRange.location, _markedRange.length,
+ _selectedRange.location, _selectedRange.length);
+}
+- (void)unmarkText
+{
+ //NSUInteger i;
+ NSUInteger len;
+
+ LOG(@"unmarkText");
+ len = [_tmpText length];
+ //for(i = 0; i < len; ++i)
+ // keystroke([_tmpText characterAtIndex:i]);
+ [_tmpText deleteCharactersInRange:NSMakeRange(0, len)];
+ _markedRange = NSMakeRange(NSNotFound, 0);
+ _selectedRange = NSMakeRange(0, 0);
+}
+- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText
+{
+ LOG(@"validAttributesForMarkedText");
+ return @[];
+}
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)r
+ actualRange:(NSRangePointer)actualRange
+{
+ NSRange sr;
+ NSAttributedString *s;
+
+ LOG(@"attributedSubstringForProposedRange: (%ld, %ld) (%ld, %ld)",
+ r.location, r.length, actualRange->location, actualRange->length);
+ sr = NSMakeRange(0, [_tmpText length]);
+ sr = NSIntersectionRange(sr, r);
+ if(actualRange)
+ *actualRange = sr;
+ LOG(@"use range: %ld, %ld", sr.location, sr.length);
+ s = nil;
+ if(sr.length)
+ s = [[NSAttributedString alloc]
+ initWithString:[_tmpText substringWithRange:sr]];
+ LOG(@" return %@", s);
+ return s;
+}
+- (void)insertText:(id)s
+ replacementRange:(NSRange)r
+{
+ NSUInteger i;
+ NSUInteger len;
+
+ LOG(@"insertText: %@ replacementRange: %ld, %ld", s, r.location, r.length);
+
+ [self clearInput];
+
+ len = [s length];
+ for(i = 0; i < len; ++i)
+ keystroke([s characterAtIndex:i]);
+ [_tmpText deleteCharactersInRange:NSMakeRange(0, _tmpText.length)];
+ _markedRange = NSMakeRange(NSNotFound, 0);
+ _selectedRange = NSMakeRange(0, 0);
+}
+- (NSUInteger)characterIndexForPoint:(NSPoint)point
+{
+ LOG(@"characterIndexForPoint: %g, %g", point.x, point.y);
+ return 0;
+}
+- (NSRect)firstRectForCharacterRange:(NSRange)r
+ actualRange:(NSRangePointer)actualRange
+{
+ LOG(@"firstRectForCharacterRange: (%ld, %ld) (%ld, %ld)",
+ r.location, r.length, actualRange->location, actualRange->length);
+ if(actualRange)
+ *actualRange = r;
+ return [[self window] convertRectToScreen:_lastInputRect];
+}
+- (void)doCommandBySelector:(SEL)s
+{
+ NSEvent *e;
+ NSEventModifierFlags m;
+ uint c, k;
+
+ LOG(@"doCommandBySelector (%@)", NSStringFromSelector(s));
+
+ e = [NSApp currentEvent];
+ c = [[e characters] characterAtIndex:0];
+ k = keycvt(c);
+ LOG(@"keyDown: character0: 0x%x -> 0x%x", c, k);
+ m = [e modifierFlags];
+
+ if(m & NSEventModifierFlagCommand){
+ if((m & NSEventModifierFlagShift) && 'a' <= k && k <= 'z')
+ k += 'A' - 'a';
+ if(' '<=k && k<='~')
+ k += Kcmd;
+ }
+ if(k>0)
+ keystroke(k);
+}
+
+// Helper for managing input rect approximately
+- (void)resetLastInputRect
+{
+ LOG(@"resetLastInputRect");
+ _lastInputRect.origin.x = 0.0;
+ _lastInputRect.origin.y = 0.0;
+ _lastInputRect.size.width = 0.0;
+ _lastInputRect.size.height = 0.0;
+}
+
+- (void)enlargeLastInputRect:(NSRect)r
+{
+ r.origin.y = [self bounds].size.height - r.origin.y - r.size.height;
+ _lastInputRect = NSUnionRect(_lastInputRect, r);
+ LOG(@"update last input rect (%g, %g, %g, %g)",
+ _lastInputRect.origin.x, _lastInputRect.origin.y,
+ _lastInputRect.size.width, _lastInputRect.size.height);
+}
+
+- (void)clearInput
+{
+ if(_tmpText.length){
+ uint i;
+ int l;
+ l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
+ + (_selectedRange.length > 0);
+ LOG(@"move right %d", l);
+ for(i = 0; i < l; ++i)
+ keystroke(Kright);
+ l = _tmpText.length+2+2*(_selectedRange.length > 0);
+ LOG(@"backspace %d", l);
+ for(uint i = 0; i < l; ++i)
+ keystroke(Kbs);
+ }
+}
+
+@end
+
+@implementation DrawLayer
+
+- (void)display
+{
+ id<MTLCommandBuffer> cbuf;
+ id<MTLBlitCommandEncoder> blit;
+
+ LOG(@"display");
+
+ cbuf = [commandQueue commandBuffer];
+
+ LOG(@"display query drawable");
+
+@autoreleasepool{
+ id<CAMetalDrawable> drawable;
+
+ drawable = [layer nextDrawable];
+ if(!drawable){
+ LOG(@"display couldn't get drawable");
+ [self setNeedsDisplay];
+ return;
+ }
+
+ LOG(@"display got drawable");
+
+ blit = [cbuf blitCommandEncoder];
+ [blit copyFromTexture:texture
+ sourceSlice:0
+ sourceLevel:0
+ sourceOrigin:MTLOriginMake(0, 0, 0)
+ sourceSize:MTLSizeMake(texture.width, texture.height, texture.depth)
+ toTexture:drawable.texture
+ destinationSlice:0
+ destinationLevel:0
+ destinationOrigin:MTLOriginMake(0, 0, 0)];
+ [blit endEncoding];
+
+ [cbuf presentDrawable:drawable];
+ drawable = nil;
+}
+ [cbuf addCompletedHandler:^(id<MTLCommandBuffer> cmdBuff){
+ if(cmdBuff.error){
+ NSLog(@"command buffer finished with error: %@",
+ cmdBuff.error.localizedDescription);
+ }else
+ LOG(@"command buffer finishes present drawable");
+ }];
+ [cbuf commit];
+
+ LOG(@"display commit");
+}
+
+@end
+
+static uint
+msec(void)
+{
+ return nsec()/1000000;
+}
+
+static uint
+keycvt(uint code)
+{
+ switch(code){
+ case '\r': return '\n';
+ case 127: return '\b';
+ case NSUpArrowFunctionKey: return Kup;
+ case NSDownArrowFunctionKey: return Kdown;
+ case NSLeftArrowFunctionKey: return Kleft;
+ case NSRightArrowFunctionKey: return Kright;
+ case NSInsertFunctionKey: return Kins;
+ case NSDeleteFunctionKey: return Kdel;
+ case NSHomeFunctionKey: return Khome;
+ case NSEndFunctionKey: return Kend;
+ case NSPageUpFunctionKey: return Kpgup;
+ case NSPageDownFunctionKey: return Kpgdown;
+ case NSF1FunctionKey: return KF|1;
+ case NSF2FunctionKey: return KF|2;
+ case NSF3FunctionKey: return KF|3;
+ case NSF4FunctionKey: return KF|4;
+ case NSF5FunctionKey: return KF|5;
+ case NSF6FunctionKey: return KF|6;
+ case NSF7FunctionKey: return KF|7;
+ case NSF8FunctionKey: return KF|8;
+ case NSF9FunctionKey: return KF|9;
+ case NSF10FunctionKey: return KF|10;
+ case NSF11FunctionKey: return KF|11;
+ case NSF12FunctionKey: return KF|12;
+ case NSBeginFunctionKey:
+ case NSPrintScreenFunctionKey:
+ case NSScrollLockFunctionKey:
+ case NSF13FunctionKey:
+ case NSF14FunctionKey:
+ case NSF15FunctionKey:
+ case NSF16FunctionKey:
+ case NSF17FunctionKey:
+ case NSF18FunctionKey:
+ case NSF19FunctionKey:
+ case NSF20FunctionKey:
+ case NSF21FunctionKey:
+ case NSF22FunctionKey:
+ case NSF23FunctionKey:
+ case NSF24FunctionKey:
+ case NSF25FunctionKey:
+ case NSF26FunctionKey:
+ case NSF27FunctionKey:
+ case NSF28FunctionKey:
+ case NSF29FunctionKey:
+ case NSF30FunctionKey:
+ case NSF31FunctionKey:
+ case NSF32FunctionKey:
+ case NSF33FunctionKey:
+ case NSF34FunctionKey:
+ case NSF35FunctionKey:
+ case NSPauseFunctionKey:
+ case NSSysReqFunctionKey:
+ case NSBreakFunctionKey:
+ case NSResetFunctionKey:
+ case NSStopFunctionKey:
+ case NSMenuFunctionKey:
+ case NSUserFunctionKey:
+ case NSSystemFunctionKey:
+ case NSPrintFunctionKey:
+ case NSClearLineFunctionKey:
+ case NSClearDisplayFunctionKey:
+ case NSInsertLineFunctionKey:
+ case NSDeleteLineFunctionKey:
+ case NSInsertCharFunctionKey:
+ case NSDeleteCharFunctionKey:
+ case NSPrevFunctionKey:
+ case NSNextFunctionKey:
+ case NSSelectFunctionKey:
+ case NSExecuteFunctionKey:
+ case NSUndoFunctionKey:
+ case NSRedoFunctionKey:
+ case NSFindFunctionKey:
+ case NSHelpFunctionKey:
+ case NSModeSwitchFunctionKey: return 0;
+ default: return code;
+ }
+}
+
+Memimage*
+attachscreen(char *label, char *winsize)
+{
+ LOG(@"attachscreen(%s, %s)", label, winsize);
+ [AppDelegate
+ performSelectorOnMainThread:@selector(makewin:)
+ withObject:[NSValue valueWithPointer:winsize]
+ waitUntilDone:YES];
+ kicklabel(label);
+ setcursor(nil, nil);
+ mouseresized = 0;
+ return initimg();
+}
+
+static Memimage*
+initimg(void)
+{
+@autoreleasepool{
+ CGFloat scale;
+ NSSize size;
+ MTLTextureDescriptor *textureDesc;
+
+ size = [myContent convertSizeToBacking:[myContent bounds].size];
+ mouserect = Rect(0, 0, size.width, size.height);
+
+ LOG(@"initimg %.0f %.0f", size.width, size.height);
+
+ img = allocmemimage(mouserect, XRGB32);
+ if(img == nil)
+ panic("allocmemimage: %r");
+ if(img->data == nil)
+ panic("img->data == nil");
+
+ textureDesc = [MTLTextureDescriptor
+ texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
+ width:size.width
+ height:size.height
+ mipmapped:NO];
+ textureDesc.allowGPUOptimizedContents = YES;
+ textureDesc.usage = MTLTextureUsageShaderRead;
+ textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
+ texture = [device newTextureWithDescriptor:textureDesc];
+
+ scale = [win backingScaleFactor];
+ [layer setDrawableSize:size];
+ [layer setContentsScale:scale];
+
+ // NOTE: This is not really the display DPI.
+ // On retina, scale is 2; otherwise it is 1.
+ // This formula gives us 220 for retina, 110 otherwise.
+ // That's not quite right but it's close to correct.
+ // https://en.wikipedia.org/wiki/Retina_display#Models
+ displaydpi = scale * 110;
+}
+ LOG(@"initimg return");
+
+ return img;
+}
+
+void
+_flushmemscreen(Rectangle r)
+{
+ LOG(@"_flushmemscreen(%d,%d,%d,%d)", r.min.x, r.min.y, Dx(r), Dy(r));
+ if(!rectinrect(r, Rect(0, 0, texture.width, texture.height))){
+ LOG(@"Rectangle is out of bounds, return.");
+ return;
+ }
+
+ @autoreleasepool{
+ [texture
+ replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r))
+ mipmapLevel:0
+ withBytes:byteaddr(img, Pt(r.min.x, r.min.y))
+ bytesPerRow:img->width*sizeof(u32int)];
+ [AppDelegate
+ performSelectorOnMainThread:@selector(callsetNeedsDisplayInRect:)
+ withObject:[NSValue valueWithRect:NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r))]
+ waitUntilDone:NO];
+ }
+}
+
+void
+setmouse(Point p)
+{
+ @autoreleasepool{
+ NSPoint q;
+
+ LOG(@"setmouse(%d,%d)", p.x, p.y);
+ q = [win convertPointFromBacking:NSMakePoint(p.x, p.y)];
+ LOG(@"(%g, %g) <- fromBacking", q.x, q.y);
+ q = [myContent convertPoint:q toView:nil];
+ LOG(@"(%g, %g) <- toWindow", q.x, q.y);
+ q = [win convertPointToScreen:q];
+ LOG(@"(%g, %g) <- toScreen", q.x, q.y);
+ // Quartz has the origin of the "global display
+ // coordinate space" at the top left of the primary
+ // screen with y increasing downward, while Cocoa has
+ // the origin at the bottom left of the primary screen
+ // with y increasing upward. We flip the coordinate
+ // with a negative sign and shift upward by the height
+ // of the primary screen.
+ q.y = NSScreen.screens[0].frame.size.height - q.y;
+ LOG(@"(%g, %g) <- setmouse", q.x, q.y);
+ CGWarpMouseCursorPosition(NSPointToCGPoint(q));
+ CGAssociateMouseAndMouseCursorPosition(true);
+ }
+}
+
+char*
+getsnarf(void)
+{
+ NSPasteboard *pb;
+ NSString *s;
+
+ @autoreleasepool{
+ pb = [NSPasteboard generalPasteboard];
+
+ qlock(&snarfl);
+ s = [pb stringForType:NSPasteboardTypeString];
+ qunlock(&snarfl);
+
+ if(s)
+ return strdup((char *)[s UTF8String]);
+ else
+ return nil;
+ }
+}
+
+void
+putsnarf(char *s)
+{
+ NSArray *t;
+ NSPasteboard *pb;
+ NSString *str;
+
+ if(strlen(s) >= SnarfSize)
+ return;
+
+ @autoreleasepool{
+ t = [NSArray arrayWithObject:NSPasteboardTypeString];
+ pb = [NSPasteboard generalPasteboard];
+ str = [[NSString alloc] initWithUTF8String:s];
+
+ qlock(&snarfl);
+ [pb declareTypes:t owner:nil];
+ [pb setString:str forType:NSPasteboardTypeString];
+ qunlock(&snarfl);
+ }
+}
+
+void
+kicklabel(char *label)
+{
+ NSString *s;
+
+ LOG(@"kicklabel(%s)", label);
+ if(label == nil)
+ return;
+
+ @autoreleasepool{
+ s = [[NSString alloc] initWithUTF8String:label];
+ [AppDelegate
+ performSelectorOnMainThread:@selector(callkicklabel:)
+ withObject:s
+ waitUntilDone:NO];
+ }
+}
+
+void
+setcursor(Cursor *c, Cursor2 *c2)
+{
+ Cursors cs;
+
+ cs.c = c;
+ cs.c2 = c2;
+
+ [AppDelegate
+ performSelectorOnMainThread:@selector(callsetcursor:)
+ withObject:[NSValue valueWithPointer:&cs]
+ waitUntilDone:YES];
+}
+
+void
+topwin(void)
+{
+ [win
+ performSelectorOnMainThread:
+ @selector(makeKeyAndOrderFront:)
+ withObject:nil
+ waitUntilDone:YES];
+
+ [NSApp activateIgnoringOtherApps:YES];
+}
+
+void
+resizeimg(void)
+{
+ zlock();
+ _drawreplacescreenimage(initimg());
+
+ mouseresized = 1;
+ zunlock();
+ [myContent sendmouse:0];
+}
+
+void
+resizewindow(Rectangle r)
+{
+ LOG(@"resizewindow %d %d %d %d", r.min.x, r.min.y, Dx(r), Dy(r));
+ dispatch_async(dispatch_get_main_queue(), ^(void){
+ NSSize s;
+
+ s = [myContent convertSizeFromBacking:NSMakeSize(Dx(r), Dy(r))];
+ [win setContentSize:s];
+ });
+}
+
+static void
+setprocname(const char *s)
+{
+ CFStringRef process_name;
+
+ process_name = CFStringCreateWithBytes(nil, (uchar*)s, strlen(s), kCFStringEncodingUTF8, false);
+
+ // Adapted from Chrome's mac_util.mm.
+ // http://src.chromium.org/viewvc/chrome/trunk/src/base/mac/mac_util.mm
+ //
+ // Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ //
+ // Redistribution and use in source and binary forms, with or without
+ // modification, are permitted provided that the following conditions are
+ // met:
+ //
+ // * Redistributions of source code must retain the above copyright
+ // notice, this list of conditions and the following disclaimer.
+ // * Redistributions in binary form must reproduce the above
+ // copyright notice, this list of conditions and the following disclaimer
+ // in the documentation and/or other materials provided with the
+ // distribution.
+ // * Neither the name of Google Inc. nor the names of its
+ // contributors may be used to endorse or promote products derived from
+ // this software without specific prior written permission.
+ //
+ // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ // Warning: here be dragons! This is SPI reverse-engineered from WebKit's
+ // plugin host, and could break at any time (although realistically it's only
+ // likely to break in a new major release).
+ // When 10.7 is available, check that this still works, and update this
+ // comment for 10.8.
+
+ // Private CFType used in these LaunchServices calls.
+ typedef CFTypeRef PrivateLSASN;
+ typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
+ typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
+ CFStringRef,
+ CFStringRef,
+ CFDictionaryRef*);
+
+ static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
+ NULL;
+ static LSSetApplicationInformationItemType
+ ls_set_application_information_item_func = NULL;
+ static CFStringRef ls_display_name_key = NULL;
+
+ static bool did_symbol_lookup = false;
+ if (!did_symbol_lookup) {
+ did_symbol_lookup = true;
+ CFBundleRef launch_services_bundle =
+ CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
+ if (!launch_services_bundle) {
+ fprint(2, "Failed to look up LaunchServices bundle\n");
+ return;
+ }
+
+ ls_get_current_application_asn_func =
+ (LSGetCurrentApplicationASNType)(
+ CFBundleGetFunctionPointerForName(
+ launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
+ if (!ls_get_current_application_asn_func)
+ fprint(2, "Could not find _LSGetCurrentApplicationASN\n");
+
+ ls_set_application_information_item_func =
+ (LSSetApplicationInformationItemType)(
+ CFBundleGetFunctionPointerForName(
+ launch_services_bundle,
+ CFSTR("_LSSetApplicationInformationItem")));
+ if (!ls_set_application_information_item_func)
+ fprint(2, "Could not find _LSSetApplicationInformationItem\n");
+
+ CFStringRef* key_pointer = (CFStringRef*)(
+ CFBundleGetDataPointerForName(launch_services_bundle,
+ CFSTR("_kLSDisplayNameKey")));
+ ls_display_name_key = key_pointer ? *key_pointer : NULL;
+ if (!ls_display_name_key)
+ fprint(2, "Could not find _kLSDisplayNameKey\n");
+
+ // Internally, this call relies on the Mach ports that are started up by the
+ // Carbon Process Manager. In debug builds this usually happens due to how
+ // the logging layers are started up; but in release, it isn't started in as
+ // much of a defined order. So if the symbols had to be loaded, go ahead
+ // and force a call to make sure the manager has been initialized and hence
+ // the ports are opened.
+ ProcessSerialNumber psn;
+ GetCurrentProcess(&psn);
+ }
+ if (!ls_get_current_application_asn_func ||
+ !ls_set_application_information_item_func ||
+ !ls_display_name_key) {
+ return;
+ }
+
+ PrivateLSASN asn = ls_get_current_application_asn_func();
+ // Constant used by WebKit; what exactly it means is unknown.
+ const int magic_session_constant = -2;
+ OSErr err =
+ ls_set_application_information_item_func(magic_session_constant, asn,
+ ls_display_name_key,
+ process_name,
+ NULL /* optional out param */);
+ if(err != noErr)
+ fprint(2, "Call to set process name failed\n");
+}