Blob


1 #define Cursor OSXCursor
2 #define Point OSXPoint
3 #define Rect OSXRect
5 #import <Cocoa/Cocoa.h>
6 #import <Metal/Metal.h>
7 #import <QuartzCore/CAMetalLayer.h>
9 #undef Cursor
10 #undef Point
11 #undef Rect
13 #include <u.h>
14 #include <libc.h>
15 #include "cocoa-thread.h"
16 #include <draw.h>
17 #include <memdraw.h>
18 #include <keyboard.h>
19 #include <cursor.h>
20 #include "cocoa-screen.h"
21 #include "osx-keycodes.h"
22 #include "devdraw.h"
23 #include "bigarrow.h"
24 #include "glendapng.h"
26 AUTOFRAMEWORK(Cocoa)
27 AUTOFRAMEWORK(Metal)
28 AUTOFRAMEWORK(QuartzCore)
30 #define LOG if(0)NSLog
32 static void setprocname(const char*);
33 static uint keycvt(uint);
34 static uint msec(void);
35 static Memimage* initimg(void);
37 void
38 usage(void)
39 {
40 fprint(2, "usage: devdraw (don't run directly)\n");
41 threadexitsall("usage");
42 }
44 @interface AppDelegate : NSObject<NSApplicationDelegate,NSWindowDelegate>
45 + (void)callservep9p:(id)arg;
46 + (void)makewin:(NSValue *)v;
47 + (void)callkicklabel:(NSString *)v;
48 + (void)callsetNeedsDisplayInRect:(NSValue *)v;
49 + (void)callsetcursor:(NSValue *)v;
50 @end
51 @interface DevDrawView : NSView<NSTextInputClient>
52 - (void)clearInput;
53 - (void)getmouse:(NSEvent *)e;
54 - (void)sendmouse:(NSUInteger)b;
55 - (void)resetLastInputRect;
56 - (void)enlargeLastInputRect:(NSRect)r;
57 @end
58 @interface DrawLayer : CAMetalLayer
59 @end
61 static AppDelegate *myApp = NULL;
62 static DevDrawView *myContent = NULL;
63 static NSWindow *win = NULL;
64 static NSCursor *currentCursor = NULL;
66 static DrawLayer *layer;
67 static id<MTLDevice> device;
68 static id<MTLCommandQueue> commandQueue;
69 static id<MTLTexture> texture;
71 static Memimage *img = NULL;
73 static QLock snarfl;
75 void
76 threadmain(int argc, char **argv)
77 {
78 /*
79 * Move the protocol off stdin/stdout so that
80 * any inadvertent prints don't screw things up.
81 */
82 dup(0,3);
83 dup(1,4);
84 close(0);
85 close(1);
86 open("/dev/null", OREAD);
87 open("/dev/null", OWRITE);
89 ARGBEGIN{
90 case 'D': /* for good ps -a listings */
91 break;
92 case 'f': /* fall through for backward compatibility */
93 case 'g':
94 case 'b':
95 break;
96 default:
97 usage();
98 }ARGEND
100 setprocname(argv0);
102 @autoreleasepool{
103 [NSApplication sharedApplication];
104 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
105 myApp = [AppDelegate new];
106 [NSApp setDelegate:myApp];
107 [NSApp run];
111 @implementation AppDelegate
113 + (void)callservep9p:(id)arg
115 servep9p();
116 [NSApp terminate:self];
119 + (void)makewin:(NSValue *)v
121 NSRect r, sr;
122 Rectangle wr;
123 int set;
124 char *s;
125 NSArray *allDevices;
127 const NSWindowStyleMask Winstyle = NSWindowStyleMaskTitled
128 | NSWindowStyleMaskClosable
129 | NSWindowStyleMaskMiniaturizable
130 | NSWindowStyleMaskResizable;
132 sr = [[NSScreen mainScreen] frame];
133 r = [[NSScreen mainScreen] visibleFrame];
135 s = [v pointerValue];
136 LOG(@"makewin(%s)", s);
137 if(s && *s){
138 if(parsewinsize(s, &wr, &set) < 0)
139 sysfatal("%r");
140 }else{
141 wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
142 set = 0;
145 r.origin.x = wr.min.x;
146 r.origin.y = sr.size.height-wr.max.y; /* winsize is top-left-based */
147 r.size.width = fmin(Dx(wr), r.size.width);
148 r.size.height = fmin(Dy(wr), r.size.height);
149 r = [NSWindow contentRectForFrameRect:r styleMask:Winstyle];
151 win = [[NSWindow alloc]
152 initWithContentRect:r
153 styleMask:Winstyle
154 backing:NSBackingStoreBuffered defer:NO];
155 [win setTitle:@"devdraw"];
157 if(!set)
158 [win center];
159 [win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
160 [win setContentMinSize:NSMakeSize(64,64)];
161 [win setOpaque:YES];
162 [win setRestorable:NO];
163 [win setAcceptsMouseMovedEvents:YES];
164 [win setDelegate:myApp];
166 myContent = [DevDrawView new];
167 [win setContentView:myContent];
168 [myContent setWantsLayer:YES];
169 [myContent setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
171 device = nil;
172 allDevices = MTLCopyAllDevices();
173 for(id mtlDevice in allDevices) {
174 if ([mtlDevice isLowPower] && ![mtlDevice isRemovable]) {
175 device = mtlDevice;
176 break;
179 if(!device)
180 device = MTLCreateSystemDefaultDevice();
182 commandQueue = [device newCommandQueue];
184 layer = (DrawLayer *)[myContent layer];
185 layer.device = device;
186 layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
187 layer.framebufferOnly = YES;
188 layer.opaque = YES;
190 // We use a default transparent layer on top of the CAMetalLayer.
191 // This seems to make fullscreen applications behave.
193 CALayer *stub = [CALayer layer];
194 stub.frame = CGRectMake(0, 0, 1, 1);
195 [stub setNeedsDisplay];
196 [layer addSublayer:stub];
199 [NSEvent setMouseCoalescingEnabled:NO];
201 topwin();
204 + (void)callkicklabel:(NSString *)s
206 LOG(@"callkicklabel(%@)", s);
207 [win setTitle:s];
208 [[NSApp dockTile] setBadgeLabel:s];
212 + (void)callsetNeedsDisplayInRect:(NSValue *)v
214 NSRect r;
215 dispatch_time_t time;
217 r = [v rectValue];
218 LOG(@"callsetNeedsDisplayInRect(%g, %g, %g, %g)", r.origin.x, r.origin.y, r.size.width, r.size.height);
219 r = [win convertRectFromBacking:r];
220 LOG(@"setNeedsDisplayInRect(%g, %g, %g, %g)", r.origin.x, r.origin.y, r.size.width, r.size.height);
221 [layer setNeedsDisplayInRect:r];
223 time = dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC);
224 dispatch_after(time, dispatch_get_main_queue(), ^(void){
225 [layer setNeedsDisplayInRect:r];
226 });
228 [myContent enlargeLastInputRect:r];
231 typedef struct Cursors Cursors;
232 struct Cursors {
233 Cursor *c;
234 Cursor2 *c2;
235 };
237 + (void)callsetcursor:(NSValue *)v
239 Cursors *cs;
240 Cursor *c;
241 Cursor2 *c2;
242 NSBitmapImageRep *r, *r2;
243 NSImage *i;
244 NSPoint p;
245 uchar *plane[5], *plane2[5];
246 uint b;
248 cs = [v pointerValue];
249 c = cs->c;
250 if(!c)
251 c = &bigarrow;
252 c2 = cs->c2;
253 if(!c2)
254 c2 = &bigarrow2;
256 r = [[NSBitmapImageRep alloc]
257 initWithBitmapDataPlanes:nil
258 pixelsWide:16
259 pixelsHigh:16
260 bitsPerSample:1
261 samplesPerPixel:2
262 hasAlpha:YES
263 isPlanar:YES
264 colorSpaceName:NSDeviceWhiteColorSpace
265 bytesPerRow:2
266 bitsPerPixel:0];
267 [r getBitmapDataPlanes:plane];
268 for(b=0; b<nelem(c->set); b++){
269 plane[0][b] = ~c->set[b] & c->clr[b];
270 plane[1][b] = c->set[b] | c->clr[b];
273 r2 = [[NSBitmapImageRep alloc]
274 initWithBitmapDataPlanes:nil
275 pixelsWide:32
276 pixelsHigh:32
277 bitsPerSample:1
278 samplesPerPixel:2
279 hasAlpha:YES
280 isPlanar:YES
281 colorSpaceName:NSDeviceWhiteColorSpace
282 bytesPerRow:4
283 bitsPerPixel:0];
284 [r2 getBitmapDataPlanes:plane2];
285 for(b=0; b<nelem(c2->set); b++){
286 plane2[0][b] = ~c2->set[b] & c2->clr[b];
287 plane2[1][b] = c2->set[b] | c2->clr[b];
290 // For checking out the cursor bitmap image
291 /*
292 static BOOL saveimg = YES;
293 if(saveimg){
294 NSData *data = [r representationUsingType: NSBitmapImageFileTypeBMP properties: @{}];
295 [data writeToFile: @"/tmp/r.bmp" atomically: NO];
296 data = [r2 representationUsingType: NSBitmapImageFileTypeBMP properties: @{}];
297 [data writeToFile: @"/tmp/r2.bmp" atomically: NO];
298 saveimg = NO;
300 */
302 i = [[NSImage alloc] initWithSize:NSMakeSize(16, 16)];
303 [i addRepresentation:r2];
304 [i addRepresentation:r];
306 p = NSMakePoint(-c->offset.x, -c->offset.y);
307 currentCursor = [[NSCursor alloc] initWithImage:i hotSpot:p];
309 [win invalidateCursorRectsForView:myContent];
312 - (void)applicationDidFinishLaunching:(id)arg
314 NSMenu *m, *sm;
315 NSData *d;
316 NSImage *i;
318 LOG(@"applicationDidFinishLaunching");
320 sm = [NSMenu new];
321 [sm addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
322 [sm addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"];
323 [sm addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
324 m = [NSMenu new];
325 [m addItemWithTitle:@"DEVDRAW" action:NULL keyEquivalent:@""];
326 [m setSubmenu:sm forItem:[m itemWithTitle:@"DEVDRAW"]];
327 [NSApp setMainMenu:m];
329 d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)];
330 i = [[NSImage alloc] initWithData:d];
331 [NSApp setApplicationIconImage:i];
332 [[NSApp dockTile] display];
334 [NSThread
335 detachNewThreadSelector:@selector(callservep9p:)
336 toTarget:[self class] withObject:nil];
339 - (NSApplicationPresentationOptions)window:(id)arg
340 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
341 NSApplicationPresentationOptions o;
342 o = proposedOptions;
343 o &= ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar);
344 o |= NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
345 return o;
348 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
349 return YES;
352 - (void)windowDidResize:(NSNotification *)notification
354 if(![myContent inLiveResize] && img) {
355 resizeimg();
359 - (void)windowDidBecomeKey:(id)arg
361 [myContent sendmouse:0];
364 @end
366 @implementation DevDrawView
368 NSMutableString *_tmpText;
369 NSRange _markedRange;
370 NSRange _selectedRange;
371 NSRect _lastInputRect; // The view is flipped, this is not.
372 BOOL _tapping;
373 NSUInteger _tapFingers;
374 NSUInteger _tapTime;
377 - (id)init
379 LOG(@"View init");
380 self = [super init];
381 [self setAllowedTouchTypes:NSTouchTypeMaskDirect|NSTouchTypeMaskIndirect];
382 _tmpText = [[NSMutableString alloc] initWithCapacity:2];
383 _markedRange = NSMakeRange(NSNotFound, 0);
384 _selectedRange = NSMakeRange(0, 0);
385 return self;
388 - (CALayer *)makeBackingLayer
390 LOG(@"makeBackingLayer");
391 return [DrawLayer layer];
394 - (BOOL)wantsUpdateLayer
396 return YES;
399 - (BOOL)isOpaque
401 return YES;
404 - (BOOL)isFlipped
406 return YES;
409 - (BOOL)acceptsFirstResponder
411 return YES;
414 - (void)mouseMoved:(NSEvent*)e{ [self getmouse:e];}
415 - (void)mouseDown:(NSEvent*)e{ [self getmouse:e];}
416 - (void)mouseDragged:(NSEvent*)e{ [self getmouse:e];}
417 - (void)mouseUp:(NSEvent*)e{ [self getmouse:e];}
418 - (void)otherMouseDown:(NSEvent*)e{ [self getmouse:e];}
419 - (void)otherMouseDragged:(NSEvent*)e{ [self getmouse:e];}
420 - (void)otherMouseUp:(NSEvent*)e{ [self getmouse:e];}
421 - (void)rightMouseDown:(NSEvent*)e{ [self getmouse:e];}
422 - (void)rightMouseDragged:(NSEvent*)e{ [self getmouse:e];}
423 - (void)rightMouseUp:(NSEvent*)e{ [self getmouse:e];}
425 - (void)scrollWheel:(NSEvent*)e
427 NSInteger s;
429 s = [e scrollingDeltaY];
430 if(s > 0)
431 [self sendmouse:8];
432 else if (s < 0)
433 [self sendmouse:16];
436 - (void)keyDown:(NSEvent*)e
438 LOG(@"keyDown to interpret");
440 [self interpretKeyEvents:[NSArray arrayWithObject:e]];
442 [self resetLastInputRect];
445 - (void)flagsChanged:(NSEvent*)e
447 static NSEventModifierFlags omod;
448 NSEventModifierFlags m;
449 uint b;
451 LOG(@"flagsChanged");
452 m = [e modifierFlags];
454 b = [NSEvent pressedMouseButtons];
455 b = (b&~6) | (b&4)>>1 | (b&2)<<1;
456 if(b){
457 if(m & ~omod & NSEventModifierFlagControl)
458 b |= 1;
459 if(m & ~omod & NSEventModifierFlagOption)
460 b |= 2;
461 if(m & ~omod & NSEventModifierFlagCommand)
462 b |= 4;
463 [self sendmouse:b];
464 }else if(m & ~omod & NSEventModifierFlagOption)
465 keystroke(Kalt);
467 omod = m;
470 - (void)magnifyWithEvent:(NSEvent*)e
472 if(fabs([e magnification]) > 0.02)
473 [[self window] toggleFullScreen:nil];
476 - (void)touchesBeganWithEvent:(NSEvent*)e
478 _tapping = YES;
479 _tapFingers = [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count;
480 _tapTime = msec();
482 - (void)touchesMovedWithEvent:(NSEvent*)e
484 _tapping = NO;
486 - (void)touchesEndedWithEvent:(NSEvent*)e
488 if(_tapping
489 && [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count == 0
490 && msec() - _tapTime < 250){
491 switch(_tapFingers){
492 case 3:
493 [self sendmouse:2];
494 [self sendmouse:0];
495 break;
496 case 4:
497 [self sendmouse:2];
498 [self sendmouse:1];
499 [self sendmouse:0];
500 break;
502 _tapping = NO;
505 - (void)touchesCancelledWithEvent:(NSEvent*)e
507 _tapping = NO;
510 - (void)getmouse:(NSEvent *)e
512 NSUInteger b;
513 NSEventModifierFlags m;
515 b = [NSEvent pressedMouseButtons];
516 b = b&~6 | (b&4)>>1 | (b&2)<<1;
517 b = mouseswap(b);
519 if(b == 1){
520 m = [e modifierFlags];
521 if(m & NSEventModifierFlagOption){
522 abortcompose();
523 b = 2;
524 }else
525 if(m & NSEventModifierFlagCommand)
526 b = 4;
528 [self sendmouse:b];
531 - (void)sendmouse:(NSUInteger)b
533 NSPoint p;
535 p = [self.window convertPointToBacking:
536 [self.window mouseLocationOutsideOfEventStream]];
537 p.y = Dy(mouserect) - p.y;
538 // LOG(@"(%g, %g) <- sendmouse(%d)", p.x, p.y, (uint)b);
539 mousetrack(p.x, p.y, b, msec());
540 if(b && _lastInputRect.size.width && _lastInputRect.size.height)
541 [self resetLastInputRect];
544 - (void)resetCursorRects {
545 [super resetCursorRects];
546 [self addCursorRect:self.bounds cursor:currentCursor];
549 - (void)viewDidEndLiveResize
551 [super viewDidEndLiveResize];
552 if(img)
553 resizeimg();
556 - (void)viewDidChangeBackingProperties
558 [super viewDidChangeBackingProperties];
559 if(img)
560 resizeimg();
563 // conforms to protocol NSTextInputClient
564 - (BOOL)hasMarkedText
566 LOG(@"hasMarkedText");
567 return _markedRange.location != NSNotFound;
569 - (NSRange)markedRange
571 LOG(@"markedRange");
572 return _markedRange;
574 - (NSRange)selectedRange
576 LOG(@"selectedRange");
577 return _selectedRange;
579 - (void)setMarkedText:(id)string
580 selectedRange:(NSRange)sRange
581 replacementRange:(NSRange)rRange
583 NSString *str;
585 LOG(@"setMarkedText: %@ (%ld, %ld) (%ld, %ld)", string,
586 sRange.location, sRange.length,
587 rRange.location, rRange.length);
589 [self clearInput];
591 if([string isKindOfClass:[NSAttributedString class]])
592 str = [string string];
593 else
594 str = string;
596 if(rRange.location == NSNotFound){
597 if(_markedRange.location != NSNotFound){
598 rRange = _markedRange;
599 }else{
600 rRange = _selectedRange;
604 if(str.length == 0){
605 [_tmpText deleteCharactersInRange:rRange];
606 [self unmarkText];
607 }else{
608 _markedRange = NSMakeRange(rRange.location, str.length);
609 [_tmpText replaceCharactersInRange:rRange withString:str];
611 _selectedRange.location = rRange.location + sRange.location;
612 _selectedRange.length = sRange.length;
614 if(_tmpText.length){
615 uint i;
616 LOG(@"text length %ld", _tmpText.length);
617 for(i = 0; i <= _tmpText.length; ++i){
618 if(i == _markedRange.location)
619 keystroke('[');
620 if(_selectedRange.length){
621 if(i == _selectedRange.location)
622 keystroke('{');
623 if(i == NSMaxRange(_selectedRange))
624 keystroke('}');
626 if(i == NSMaxRange(_markedRange))
627 keystroke(']');
628 if(i < _tmpText.length)
629 keystroke([_tmpText characterAtIndex:i]);
631 int l;
632 l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
633 + (_selectedRange.length > 0);
634 LOG(@"move left %d", l);
635 for(i = 0; i < l; ++i)
636 keystroke(Kleft);
639 LOG(@"text: \"%@\" (%ld,%ld) (%ld,%ld)", _tmpText,
640 _markedRange.location, _markedRange.length,
641 _selectedRange.location, _selectedRange.length);
643 - (void)unmarkText
645 //NSUInteger i;
646 NSUInteger len;
648 LOG(@"unmarkText");
649 len = [_tmpText length];
650 //for(i = 0; i < len; ++i)
651 // keystroke([_tmpText characterAtIndex:i]);
652 [_tmpText deleteCharactersInRange:NSMakeRange(0, len)];
653 _markedRange = NSMakeRange(NSNotFound, 0);
654 _selectedRange = NSMakeRange(0, 0);
656 - (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText
658 LOG(@"validAttributesForMarkedText");
659 return @[];
661 - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)r
662 actualRange:(NSRangePointer)actualRange
664 NSRange sr;
665 NSAttributedString *s;
667 LOG(@"attributedSubstringForProposedRange: (%ld, %ld) (%ld, %ld)",
668 r.location, r.length, actualRange->location, actualRange->length);
669 sr = NSMakeRange(0, [_tmpText length]);
670 sr = NSIntersectionRange(sr, r);
671 if(actualRange)
672 *actualRange = sr;
673 LOG(@"use range: %ld, %ld", sr.location, sr.length);
674 if(sr.length)
675 s = [[NSAttributedString alloc]
676 initWithString:[_tmpText substringWithRange:sr]];
677 LOG(@" return %@", s);
678 return s;
680 - (void)insertText:(id)s
681 replacementRange:(NSRange)r
683 NSUInteger i;
684 NSUInteger len;
686 LOG(@"insertText: %@ replacementRange: %ld, %ld", s, r.location, r.length);
688 [self clearInput];
690 len = [s length];
691 for(i = 0; i < len; ++i)
692 keystroke([s characterAtIndex:i]);
693 [_tmpText deleteCharactersInRange:NSMakeRange(0, _tmpText.length)];
694 _markedRange = NSMakeRange(NSNotFound, 0);
695 _selectedRange = NSMakeRange(0, 0);
697 - (NSUInteger)characterIndexForPoint:(NSPoint)point
699 LOG(@"characterIndexForPoint: %g, %g", point.x, point.y);
700 return 0;
702 - (NSRect)firstRectForCharacterRange:(NSRange)r
703 actualRange:(NSRangePointer)actualRange
705 LOG(@"firstRectForCharacterRange: (%ld, %ld) (%ld, %ld)",
706 r.location, r.length, actualRange->location, actualRange->length);
707 if(actualRange)
708 *actualRange = r;
709 return [[self window] convertRectToScreen:_lastInputRect];
711 - (void)doCommandBySelector:(SEL)s
713 NSEvent *e;
714 NSEventModifierFlags m;
715 uint c, k;
717 LOG(@"doCommandBySelector (%@)", NSStringFromSelector(s));
719 e = [NSApp currentEvent];
720 c = [[e characters] characterAtIndex:0];
721 k = keycvt(c);
722 LOG(@"keyDown: character0: 0x%x -> 0x%x", c, k);
723 m = [e modifierFlags];
725 if(m & NSEventModifierFlagCommand){
726 if((m & NSEventModifierFlagShift) && 'a' <= k && k <= 'z')
727 k += 'A' - 'a';
728 if(' '<=k && k<='~')
729 k += Kcmd;
731 if(k>0)
732 keystroke(k);
735 // Helper for managing input rect approximately
736 - (void)resetLastInputRect
738 LOG(@"resetLastInputRect");
739 _lastInputRect.origin.x = 0.0;
740 _lastInputRect.origin.y = 0.0;
741 _lastInputRect.size.width = 0.0;
742 _lastInputRect.size.height = 0.0;
745 - (void)enlargeLastInputRect:(NSRect)r
747 r.origin.y = [self bounds].size.height - r.origin.y - r.size.height;
748 _lastInputRect = NSUnionRect(_lastInputRect, r);
749 LOG(@"update last input rect (%g, %g, %g, %g)",
750 _lastInputRect.origin.x, _lastInputRect.origin.y,
751 _lastInputRect.size.width, _lastInputRect.size.height);
754 - (void)clearInput
756 if(_tmpText.length){
757 uint i;
758 int l;
759 l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
760 + (_selectedRange.length > 0);
761 LOG(@"move right %d", l);
762 for(i = 0; i < l; ++i)
763 keystroke(Kright);
764 l = _tmpText.length+2+2*(_selectedRange.length > 0);
765 LOG(@"backspace %d", l);
766 for(uint i = 0; i < l; ++i)
767 keystroke(Kbs);
771 @end
773 @implementation DrawLayer
775 - (void)display
777 id<MTLCommandBuffer> cbuf;
778 id<MTLBlitCommandEncoder> blit;
780 LOG(@"display");
782 cbuf = [commandQueue commandBuffer];
784 LOG(@"display query drawable");
786 @autoreleasepool{
787 id<CAMetalDrawable> drawable;
789 drawable = [layer nextDrawable];
790 if(!drawable){
791 LOG(@"display couldn't get drawable");
792 [self setNeedsDisplay];
793 return;
796 LOG(@"display got drawable");
798 blit = [cbuf blitCommandEncoder];
799 [blit copyFromTexture:texture
800 sourceSlice:0
801 sourceLevel:0
802 sourceOrigin:MTLOriginMake(0, 0, 0)
803 sourceSize:MTLSizeMake(texture.width, texture.height, texture.depth)
804 toTexture:drawable.texture
805 destinationSlice:0
806 destinationLevel:0
807 destinationOrigin:MTLOriginMake(0, 0, 0)];
808 [blit endEncoding];
810 [cbuf presentDrawable:drawable];
811 drawable = nil;
813 [cbuf addCompletedHandler:^(id<MTLCommandBuffer> cmdBuff){
814 if(cmdBuff.error){
815 NSLog(@"command buffer finished with error: %@",
816 cmdBuff.error.localizedDescription);
817 }else
818 LOG(@"command buffer finishes present drawable");
819 }];
820 [cbuf commit];
822 LOG(@"display commit");
825 @end
827 static uint
828 msec(void)
830 return nsec()/1000000;
833 static uint
834 keycvt(uint code)
836 switch(code){
837 case '\r': return '\n';
838 case 127: return '\b';
839 case NSUpArrowFunctionKey: return Kup;
840 case NSDownArrowFunctionKey: return Kdown;
841 case NSLeftArrowFunctionKey: return Kleft;
842 case NSRightArrowFunctionKey: return Kright;
843 case NSInsertFunctionKey: return Kins;
844 case NSDeleteFunctionKey: return Kdel;
845 case NSHomeFunctionKey: return Khome;
846 case NSEndFunctionKey: return Kend;
847 case NSPageUpFunctionKey: return Kpgup;
848 case NSPageDownFunctionKey: return Kpgdown;
849 case NSF1FunctionKey: return KF|1;
850 case NSF2FunctionKey: return KF|2;
851 case NSF3FunctionKey: return KF|3;
852 case NSF4FunctionKey: return KF|4;
853 case NSF5FunctionKey: return KF|5;
854 case NSF6FunctionKey: return KF|6;
855 case NSF7FunctionKey: return KF|7;
856 case NSF8FunctionKey: return KF|8;
857 case NSF9FunctionKey: return KF|9;
858 case NSF10FunctionKey: return KF|10;
859 case NSF11FunctionKey: return KF|11;
860 case NSF12FunctionKey: return KF|12;
861 case NSBeginFunctionKey:
862 case NSPrintScreenFunctionKey:
863 case NSScrollLockFunctionKey:
864 case NSF13FunctionKey:
865 case NSF14FunctionKey:
866 case NSF15FunctionKey:
867 case NSF16FunctionKey:
868 case NSF17FunctionKey:
869 case NSF18FunctionKey:
870 case NSF19FunctionKey:
871 case NSF20FunctionKey:
872 case NSF21FunctionKey:
873 case NSF22FunctionKey:
874 case NSF23FunctionKey:
875 case NSF24FunctionKey:
876 case NSF25FunctionKey:
877 case NSF26FunctionKey:
878 case NSF27FunctionKey:
879 case NSF28FunctionKey:
880 case NSF29FunctionKey:
881 case NSF30FunctionKey:
882 case NSF31FunctionKey:
883 case NSF32FunctionKey:
884 case NSF33FunctionKey:
885 case NSF34FunctionKey:
886 case NSF35FunctionKey:
887 case NSPauseFunctionKey:
888 case NSSysReqFunctionKey:
889 case NSBreakFunctionKey:
890 case NSResetFunctionKey:
891 case NSStopFunctionKey:
892 case NSMenuFunctionKey:
893 case NSUserFunctionKey:
894 case NSSystemFunctionKey:
895 case NSPrintFunctionKey:
896 case NSClearLineFunctionKey:
897 case NSClearDisplayFunctionKey:
898 case NSInsertLineFunctionKey:
899 case NSDeleteLineFunctionKey:
900 case NSInsertCharFunctionKey:
901 case NSDeleteCharFunctionKey:
902 case NSPrevFunctionKey:
903 case NSNextFunctionKey:
904 case NSSelectFunctionKey:
905 case NSExecuteFunctionKey:
906 case NSUndoFunctionKey:
907 case NSRedoFunctionKey:
908 case NSFindFunctionKey:
909 case NSHelpFunctionKey:
910 case NSModeSwitchFunctionKey: return 0;
911 default: return code;
915 Memimage*
916 attachscreen(char *label, char *winsize)
918 LOG(@"attachscreen(%s, %s)", label, winsize);
919 [AppDelegate
920 performSelectorOnMainThread:@selector(makewin:)
921 withObject:[NSValue valueWithPointer:winsize]
922 waitUntilDone:YES];
923 kicklabel(label);
924 setcursor(nil, nil);
925 mouseresized = 0;
926 return initimg();
929 static Memimage*
930 initimg(void)
932 @autoreleasepool{
933 CGFloat scale;
934 NSSize size;
935 MTLTextureDescriptor *textureDesc;
937 size = [myContent convertSizeToBacking:[myContent bounds].size];
938 mouserect = Rect(0, 0, size.width, size.height);
940 LOG(@"initimg %.0f %.0f", size.width, size.height);
942 img = allocmemimage(mouserect, XRGB32);
943 if(img == nil)
944 panic("allocmemimage: %r");
945 if(img->data == nil)
946 panic("img->data == nil");
948 textureDesc = [MTLTextureDescriptor
949 texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
950 width:size.width
951 height:size.height
952 mipmapped:NO];
953 textureDesc.allowGPUOptimizedContents = YES;
954 textureDesc.usage = MTLTextureUsageShaderRead;
955 textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
956 texture = [device newTextureWithDescriptor:textureDesc];
958 scale = [win backingScaleFactor];
959 [layer setDrawableSize:size];
960 [layer setContentsScale:scale];
962 // NOTE: This is not really the display DPI.
963 // On retina, scale is 2; otherwise it is 1.
964 // This formula gives us 220 for retina, 110 otherwise.
965 // That's not quite right but it's close to correct.
966 // https://en.wikipedia.org/wiki/Retina_display#Models
967 displaydpi = scale * 110;
969 LOG(@"initimg return");
971 return img;
974 void
975 _flushmemscreen(Rectangle r)
977 LOG(@"_flushmemscreen(%d,%d,%d,%d)", r.min.x, r.min.y, Dx(r), Dy(r));
978 if(!rectinrect(r, Rect(0, 0, texture.width, texture.height))){
979 LOG(@"Rectangle is out of bounds, return.");
980 return;
983 @autoreleasepool{
984 [texture
985 replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r))
986 mipmapLevel:0
987 withBytes:byteaddr(img, Pt(r.min.x, r.min.y))
988 bytesPerRow:img->width*sizeof(u32int)];
989 [AppDelegate
990 performSelectorOnMainThread:@selector(callsetNeedsDisplayInRect:)
991 withObject:[NSValue valueWithRect:NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r))]
992 waitUntilDone:NO];
996 void
997 setmouse(Point p)
999 @autoreleasepool{
1000 NSPoint q;
1002 LOG(@"setmouse(%d,%d)", p.x, p.y);
1003 q = [win convertPointFromBacking:NSMakePoint(p.x, p.y)];
1004 LOG(@"(%g, %g) <- fromBacking", q.x, q.y);
1005 q = [myContent convertPoint:q toView:nil];
1006 LOG(@"(%g, %g) <- toWindow", q.x, q.y);
1007 q = [win convertPointToScreen:q];
1008 LOG(@"(%g, %g) <- toScreen", q.x, q.y);
1009 // Quartz has the origin of the "global display
1010 // coordinate space" at the top left of the primary
1011 // screen with y increasing downward, while Cocoa has
1012 // the origin at the bottom left of the primary screen
1013 // with y increasing upward. We flip the coordinate
1014 // with a negative sign and shift upward by the height
1015 // of the primary screen.
1016 q.y = NSScreen.screens[0].frame.size.height - q.y;
1017 LOG(@"(%g, %g) <- setmouse", q.x, q.y);
1018 CGWarpMouseCursorPosition(NSPointToCGPoint(q));
1019 CGAssociateMouseAndMouseCursorPosition(true);
1023 char*
1024 getsnarf(void)
1026 NSPasteboard *pb;
1027 NSString *s;
1029 @autoreleasepool{
1030 pb = [NSPasteboard generalPasteboard];
1032 qlock(&snarfl);
1033 s = [pb stringForType:NSPasteboardTypeString];
1034 qunlock(&snarfl);
1036 if(s)
1037 return strdup((char *)[s UTF8String]);
1038 else
1039 return nil;
1043 void
1044 putsnarf(char *s)
1046 NSArray *t;
1047 NSPasteboard *pb;
1048 NSString *str;
1050 if(strlen(s) >= SnarfSize)
1051 return;
1053 @autoreleasepool{
1054 t = [NSArray arrayWithObject:NSPasteboardTypeString];
1055 pb = [NSPasteboard generalPasteboard];
1056 str = [[NSString alloc] initWithUTF8String:s];
1058 qlock(&snarfl);
1059 [pb declareTypes:t owner:nil];
1060 [pb setString:str forType:NSPasteboardTypeString];
1061 qunlock(&snarfl);
1065 void
1066 kicklabel(char *label)
1068 NSString *s;
1070 LOG(@"kicklabel(%s)", label);
1071 if(label == nil)
1072 return;
1074 @autoreleasepool{
1075 s = [[NSString alloc] initWithUTF8String:label];
1076 [AppDelegate
1077 performSelectorOnMainThread:@selector(callkicklabel:)
1078 withObject:s
1079 waitUntilDone:NO];
1083 void
1084 setcursor(Cursor *c, Cursor2 *c2)
1086 Cursors cs;
1088 cs.c = c;
1089 cs.c2 = c2;
1091 [AppDelegate
1092 performSelectorOnMainThread:@selector(callsetcursor:)
1093 withObject:[NSValue valueWithPointer:&cs]
1094 waitUntilDone:YES];
1097 void
1098 topwin(void)
1100 [win
1101 performSelectorOnMainThread:
1102 @selector(makeKeyAndOrderFront:)
1103 withObject:nil
1104 waitUntilDone:YES];
1106 [NSApp activateIgnoringOtherApps:YES];
1109 void
1110 resizeimg(void)
1112 zlock();
1113 _drawreplacescreenimage(initimg());
1115 mouseresized = 1;
1116 zunlock();
1117 [myContent sendmouse:0];
1120 void
1121 resizewindow(Rectangle r)
1123 LOG(@"resizewindow %d %d %d %d", r.min.x, r.min.y, Dx(r), Dy(r));
1124 dispatch_async(dispatch_get_main_queue(), ^(void){
1125 NSSize s;
1127 s = [myContent convertSizeFromBacking:NSMakeSize(Dx(r), Dy(r))];
1128 [win setContentSize:s];
1129 });
1132 static void
1133 setprocname(const char *s)
1135 CFStringRef process_name;
1137 process_name = CFStringCreateWithBytes(nil, (uchar*)s, strlen(s), kCFStringEncodingUTF8, false);
1139 // Adapted from Chrome's mac_util.mm.
1140 // http://src.chromium.org/viewvc/chrome/trunk/src/base/mac/mac_util.mm
1142 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
1144 // Redistribution and use in source and binary forms, with or without
1145 // modification, are permitted provided that the following conditions are
1146 // met:
1148 // * Redistributions of source code must retain the above copyright
1149 // notice, this list of conditions and the following disclaimer.
1150 // * Redistributions in binary form must reproduce the above
1151 // copyright notice, this list of conditions and the following disclaimer
1152 // in the documentation and/or other materials provided with the
1153 // distribution.
1154 // * Neither the name of Google Inc. nor the names of its
1155 // contributors may be used to endorse or promote products derived from
1156 // this software without specific prior written permission.
1158 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1159 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1160 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1161 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1162 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
1163 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
1164 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1165 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
1166 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1167 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1168 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1169 // Warning: here be dragons! This is SPI reverse-engineered from WebKit's
1170 // plugin host, and could break at any time (although realistically it's only
1171 // likely to break in a new major release).
1172 // When 10.7 is available, check that this still works, and update this
1173 // comment for 10.8.
1175 // Private CFType used in these LaunchServices calls.
1176 typedef CFTypeRef PrivateLSASN;
1177 typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
1178 typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
1179 CFStringRef,
1180 CFStringRef,
1181 CFDictionaryRef*);
1183 static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
1184 NULL;
1185 static LSSetApplicationInformationItemType
1186 ls_set_application_information_item_func = NULL;
1187 static CFStringRef ls_display_name_key = NULL;
1189 static bool did_symbol_lookup = false;
1190 if (!did_symbol_lookup) {
1191 did_symbol_lookup = true;
1192 CFBundleRef launch_services_bundle =
1193 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
1194 if (!launch_services_bundle) {
1195 fprint(2, "Failed to look up LaunchServices bundle\n");
1196 return;
1199 ls_get_current_application_asn_func =
1200 (LSGetCurrentApplicationASNType)(
1201 CFBundleGetFunctionPointerForName(
1202 launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
1203 if (!ls_get_current_application_asn_func)
1204 fprint(2, "Could not find _LSGetCurrentApplicationASN\n");
1206 ls_set_application_information_item_func =
1207 (LSSetApplicationInformationItemType)(
1208 CFBundleGetFunctionPointerForName(
1209 launch_services_bundle,
1210 CFSTR("_LSSetApplicationInformationItem")));
1211 if (!ls_set_application_information_item_func)
1212 fprint(2, "Could not find _LSSetApplicationInformationItem\n");
1214 CFStringRef* key_pointer = (CFStringRef*)(
1215 CFBundleGetDataPointerForName(launch_services_bundle,
1216 CFSTR("_kLSDisplayNameKey")));
1217 ls_display_name_key = key_pointer ? *key_pointer : NULL;
1218 if (!ls_display_name_key)
1219 fprint(2, "Could not find _kLSDisplayNameKey\n");
1221 // Internally, this call relies on the Mach ports that are started up by the
1222 // Carbon Process Manager. In debug builds this usually happens due to how
1223 // the logging layers are started up; but in release, it isn't started in as
1224 // much of a defined order. So if the symbols had to be loaded, go ahead
1225 // and force a call to make sure the manager has been initialized and hence
1226 // the ports are opened.
1227 ProcessSerialNumber psn;
1228 GetCurrentProcess(&psn);
1230 if (!ls_get_current_application_asn_func ||
1231 !ls_set_application_information_item_func ||
1232 !ls_display_name_key) {
1233 return;
1236 PrivateLSASN asn = ls_get_current_application_asn_func();
1237 // Constant used by WebKit; what exactly it means is unknown.
1238 const int magic_session_constant = -2;
1239 OSErr err =
1240 ls_set_application_information_item_func(magic_session_constant, asn,
1241 ls_display_name_key,
1242 process_name,
1243 NULL /* optional out param */);
1244 if(err != noErr)
1245 fprint(2, "Call to set process name failed\n");