plan9port/src/cmd/devdraw/cocoa-screen-metal.m

1260 lines
32 KiB
Mathematica
Raw Normal View History

#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 "cocoa-thread.h"
#include <draw.h>
#include <memdraw.h>
#include <keyboard.h>
#include <cursor.h>
#include "cocoa-screen.h"
#include "osx-keycodes.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)callservep9p:(id)arg;
+ (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 MTLRenderPassDescriptor *renderPass;
static id<MTLDevice> device;
static id<MTLRenderPipelineState> pipelineState;
static id<MTLCommandQueue> commandQueue;
static id<MTLTexture> texture;
static Memimage *img = NULL;
static QLock snarfl;
static NSString *const metal =
@"#include<metal_stdlib>\n"
"using namespace metal;\n"
"typedef struct {\n"
" float4 rCoord [[position]];\n"
" float2 tCoord;\n"
"} VertexOut;\n"
"vertex VertexOut\n"
"renderVertex(unsigned int vid [[ vertex_id ]])\n"
"{\n"
" const VertexOut fixedV[] = {\n"
" {{ -1.0f, -1.0f, 0.0f, 1.0f }, { 0.0f, 1.0f }},\n"
" {{ 1.0f, -1.0f, 0.0f, 1.0f }, { 1.0f, 1.0f }},\n"
" {{ -1.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f }},\n"
" {{ 1.0f, 1.0f, 0.0f, 1.0f }, { 1.0f, 0.0f }},\n"
" };\n"
" return fixedV[vid];\n"
"}\n"
"fragment half4\n"
"renderFragment(VertexOut in [[ stage_in ]],\n"
" texture2d<half> texture [[ texture(0) ]]) {\n"
" constexpr sampler s;\n"
" return texture.sample(s, in.tCoord);\n"
"}";
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];
}
}
@implementation AppDelegate
+ (void)callservep9p:(id)arg
{
servep9p();
[NSApp terminate:self];
}
+ (void)makewin:(NSValue *)v
{
NSRect r, sr;
Rectangle wr;
int set;
char *s;
id<MTLLibrary> library;
MTLRenderPipelineDescriptor *pipelineDesc;
NSError *error;
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 setRestorable:NO];
[win setAcceptsMouseMovedEvents:YES];
[win setDelegate:myApp];
myContent = [DevDrawView new];
[win setContentView:myContent];
[myContent setWantsLayer:YES];
[myContent setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
device = MTLCreateSystemDefaultDevice();
commandQueue = [device newCommandQueue];
layer = (DrawLayer *)[myContent layer];
layer.device = device;
layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
layer.framebufferOnly = YES;
layer.opaque = YES;
renderPass = [MTLRenderPassDescriptor renderPassDescriptor];
renderPass.colorAttachments[0].loadAction = MTLLoadActionDontCare;
renderPass.colorAttachments[0].storeAction = MTLStoreActionDontCare;
library = [device newLibraryWithSource:metal options:nil error:&error];
if(!library)
sysfatal((char *)[[error localizedDescription] UTF8String]);
pipelineDesc = [MTLRenderPipelineDescriptor new];
pipelineDesc.alphaToOneEnabled = YES;
pipelineDesc.inputPrimitiveTopology = MTLPrimitiveTopologyClassTriangle;
pipelineDesc.rasterSampleCount = 1;
pipelineDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
[pipelineDesc setVertexFunction: [library newFunctionWithName: @"renderVertex"]];
[pipelineDesc setFragmentFunction: [library newFunctionWithName: @"renderFragment"]];
pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDesc error:&error];
if(!pipelineState)
sysfatal((char *)[[error localizedDescription] UTF8String]);
[NSEvent setMouseCoalescingEnabled:NO];
topwin();
}
+ (void)callkicklabel:(NSString *)s
{
LOG(@"callkicklabel(%@)", s);
[win setTitle:s];
[[NSApp dockTile] setBadgeLabel:s];
}
+ (void)callsetNeedsDisplayInRect:(NSValue *)v
{
NSRect r;
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];
[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];
int 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];
[NSThread
detachNewThreadSelector:@selector(callservep9p:)
toTarget:[self class] withObject:nil];
}
- (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)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];
resizeimg();
}
- (void)viewDidChangeBackingProperties
{
[super viewDidChangeBackingProperties];
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);
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<MTLRenderCommandEncoder> cmd;
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");
renderPass.colorAttachments[0].texture = drawable.texture;
cmd = [cbuf renderCommandEncoderWithDescriptor:renderPass];
[cmd setRenderPipelineState:pipelineState];
[cmd setFragmentTexture:texture atIndex:0];
[cmd drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[cmd 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 '\b': return 127;
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];
resizeimg();
});
}
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");
}