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 <thread.h>
16 #include <draw.h>
17 #include <memdraw.h>
18 #include <memlayer.h>
19 #include <mouse.h>
20 #include <cursor.h>
21 #include <keyboard.h>
22 #include <drawfcall.h>
23 #include "devdraw.h"
24 #include "bigarrow.h"
25 #include "glendapng.h"
27 AUTOFRAMEWORK(Cocoa)
28 AUTOFRAMEWORK(Metal)
29 AUTOFRAMEWORK(QuartzCore)
30 AUTOFRAMEWORK(CoreFoundation)
32 #define LOG if(0)NSLog
34 // TODO: Maintain list of views for dock menu.
36 static void setprocname(const char*);
37 static uint keycvt(uint);
38 static uint msec(void);
40 static void rpc_resizeimg(Client*);
41 static void rpc_resizewindow(Client*, Rectangle);
42 static void rpc_setcursor(Client*, Cursor*, Cursor2*);
43 static void rpc_setlabel(Client*, char*);
44 static void rpc_setmouse(Client*, Point);
45 static void rpc_topwin(Client*);
46 static void rpc_bouncemouse(Client*, Mouse);
47 static void rpc_flush(Client*, Rectangle);
49 static ClientImpl macimpl = {
50 rpc_resizeimg,
51 rpc_resizewindow,
52 rpc_setcursor,
53 rpc_setlabel,
54 rpc_setmouse,
55 rpc_topwin,
56 rpc_bouncemouse,
57 rpc_flush
58 };
60 @class DrawView;
61 @class DrawLayer;
63 @interface AppDelegate : NSObject<NSApplicationDelegate>
64 @end
66 static AppDelegate *myApp = NULL;
68 void
69 gfx_main(void)
70 {
71 if(client0)
72 setprocname(argv0);
74 @autoreleasepool{
75 [NSApplication sharedApplication];
76 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
77 myApp = [AppDelegate new];
78 [NSApp setDelegate:myApp];
79 [NSApp run];
80 }
81 }
84 void
85 rpc_shutdown(void)
86 {
87 [NSApp terminate:myApp];
88 }
90 @implementation AppDelegate
91 - (void)applicationDidFinishLaunching:(id)arg
92 {
93 NSMenu *m, *sm;
94 NSData *d;
95 NSImage *i;
97 LOG(@"applicationDidFinishLaunching");
99 sm = [NSMenu new];
100 [sm addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
101 [sm addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"];
102 [sm addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
103 m = [NSMenu new];
104 [m addItemWithTitle:@"DEVDRAW" action:NULL keyEquivalent:@""];
105 [m setSubmenu:sm forItem:[m itemWithTitle:@"DEVDRAW"]];
106 [NSApp setMainMenu:m];
108 d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)];
109 i = [[NSImage alloc] initWithData:d];
110 [NSApp setApplicationIconImage:i];
111 [[NSApp dockTile] display];
113 gfx_started();
116 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
117 return client0 != nil;
119 @end
121 @interface DrawLayer : CAMetalLayer
122 @property (nonatomic, retain) id<MTLCommandQueue> cmd;
123 @property (nonatomic, retain) id<MTLTexture> texture;
124 @end
126 @implementation DrawLayer
127 - (void)display
129 LOG(@"display");
130 LOG(@"display query drawable");
132 @autoreleasepool{
133 id<CAMetalDrawable> drawable = [self nextDrawable];
134 if(!drawable){
135 LOG(@"display couldn't get drawable");
136 [self setNeedsDisplay];
137 return;
140 LOG(@"display got drawable");
142 id<MTLCommandBuffer> cbuf = [self.cmd commandBuffer];
143 id<MTLBlitCommandEncoder> blit = [cbuf blitCommandEncoder];
144 [blit copyFromTexture:self.texture
145 sourceSlice:0
146 sourceLevel:0
147 sourceOrigin:MTLOriginMake(0, 0, 0)
148 sourceSize:MTLSizeMake(self.texture.width, self.texture.height, self.texture.depth)
149 toTexture:drawable.texture
150 destinationSlice:0
151 destinationLevel:0
152 destinationOrigin:MTLOriginMake(0, 0, 0)];
153 [blit endEncoding];
155 [cbuf presentDrawable:drawable];
156 drawable = nil;
157 [cbuf addCompletedHandler:^(id<MTLCommandBuffer> cmdBuff){
158 if(cmdBuff.error){
159 NSLog(@"command buffer finished with error: %@",
160 cmdBuff.error.localizedDescription);
161 }else
162 LOG(@"command buffer finishes present drawable");
163 }];
164 [cbuf commit];
166 LOG(@"display commit");
168 @end
170 @interface DrawView : NSView<NSTextInputClient,NSWindowDelegate>
171 @property (nonatomic, assign) Client *client;
172 @property (nonatomic, retain) DrawLayer *dlayer;
173 @property (nonatomic, retain) NSWindow *win;
174 @property (nonatomic, retain) NSCursor *currentCursor;
175 @property (nonatomic, assign) Memimage *img;
177 - (id)attach:(Client*)client winsize:(char*)winsize label:(char*)label;
178 - (void)topwin;
179 - (void)setlabel:(char*)label;
180 - (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2;
181 - (void)setmouse:(Point)p;
182 - (void)clearInput;
183 - (void)getmouse:(NSEvent*)e;
184 - (void)sendmouse:(NSUInteger)b;
185 - (void)resetLastInputRect;
186 - (void)enlargeLastInputRect:(NSRect)r;
187 @end
189 @implementation DrawView
191 NSMutableString *_tmpText;
192 NSRange _markedRange;
193 NSRange _selectedRange;
194 NSRect _lastInputRect; // The view is flipped, this is not.
195 BOOL _tapping;
196 NSUInteger _tapFingers;
197 NSUInteger _tapTime;
200 - (id)init
202 LOG(@"View init");
203 self = [super init];
204 [self setAllowedTouchTypes:NSTouchTypeMaskDirect|NSTouchTypeMaskIndirect];
205 _tmpText = [[NSMutableString alloc] initWithCapacity:2];
206 _markedRange = NSMakeRange(NSNotFound, 0);
207 _selectedRange = NSMakeRange(0, 0);
208 return self;
211 - (CALayer*)makeBackingLayer { return [DrawLayer layer]; }
212 - (BOOL)wantsUpdateLayer { return YES; }
213 - (BOOL)isOpaque { return YES; }
214 - (BOOL)isFlipped { return YES; }
215 - (BOOL)acceptsFirstResponder { return YES; }
217 // rpc_attach allocates a new screen window with the given label and size
218 // and attaches it to client c (by setting c->view).
219 Memimage*
220 rpc_attach(Client *c, char *label, char *winsize)
222 LOG(@"attachscreen(%s, %s)", label, winsize);
224 c->impl = &macimpl;
225 dispatch_sync(dispatch_get_main_queue(), ^(void) {
226 @autoreleasepool {
227 DrawView *view = [[DrawView new] attach:c winsize:winsize label:label];
228 [view initimg];
230 });
231 return ((__bridge DrawView*)c->view).img;
234 - (id)attach:(Client*)client winsize:(char*)winsize label:(char*)label {
235 NSRect r, sr;
236 Rectangle wr;
237 int set;
238 char *s;
239 NSArray *allDevices;
241 NSWindowStyleMask Winstyle = NSWindowStyleMaskTitled
242 | NSWindowStyleMaskClosable
243 | NSWindowStyleMaskMiniaturizable
244 | NSWindowStyleMaskResizable;
246 if(label == nil || *label == '\0')
247 Winstyle &= ~NSWindowStyleMaskTitled;
249 s = winsize;
250 sr = [[NSScreen mainScreen] frame];
251 r = [[NSScreen mainScreen] visibleFrame];
253 LOG(@"makewin(%s)", s);
254 if(s == nil || *s == '\0' || parsewinsize(s, &wr, &set) < 0) {
255 wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
256 set = 0;
259 r.origin.x = wr.min.x;
260 r.origin.y = sr.size.height-wr.max.y; /* winsize is top-left-based */
261 r.size.width = fmin(Dx(wr), r.size.width);
262 r.size.height = fmin(Dy(wr), r.size.height);
263 r = [NSWindow contentRectForFrameRect:r styleMask:Winstyle];
265 NSWindow *win = [[NSWindow alloc]
266 initWithContentRect:r
267 styleMask:Winstyle
268 backing:NSBackingStoreBuffered defer:NO];
269 [win setTitle:@"devdraw"];
271 if(!set)
272 [win center];
273 [win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
274 [win setContentMinSize:NSMakeSize(64,64)];
275 [win setOpaque:YES];
276 [win setRestorable:NO];
277 [win setAcceptsMouseMovedEvents:YES];
279 client->view = CFBridgingRetain(self);
280 self.client = client;
281 self.win = win;
282 self.currentCursor = nil;
283 [win setContentView:self];
284 [win setDelegate:self];
285 [self setWantsLayer:YES];
286 [self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
288 id<MTLDevice> device = nil;
289 allDevices = MTLCopyAllDevices();
290 for(id mtlDevice in allDevices) {
291 if ([mtlDevice isLowPower] && ![mtlDevice isRemovable]) {
292 device = mtlDevice;
293 break;
296 if(!device)
297 device = MTLCreateSystemDefaultDevice();
299 DrawLayer *layer = (DrawLayer*)[self layer];
300 self.dlayer = layer;
301 layer.device = device;
302 layer.cmd = [device newCommandQueue];
303 layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
304 layer.framebufferOnly = YES;
305 layer.opaque = YES;
307 // We use a default transparent layer on top of the CAMetalLayer.
308 // This seems to make fullscreen applications behave.
309 // Specifically, without this code if you enter full screen with Cmd-F,
310 // the screen goes black until the first mouse click.
311 if(1) {
312 CALayer *stub = [CALayer layer];
313 stub.frame = CGRectMake(0, 0, 1, 1);
314 [stub setNeedsDisplay];
315 [layer addSublayer:stub];
318 [NSEvent setMouseCoalescingEnabled:NO];
320 [self topwin];
321 [self setlabel:label];
322 [self setcursor:nil cursor2:nil];
324 return self;
327 // rpc_topwin moves the window to the top of the desktop.
328 // Called from an RPC thread with no client lock held.
329 static void
330 rpc_topwin(Client *c)
332 DrawView *view = (__bridge DrawView*)c->view;
333 dispatch_sync(dispatch_get_main_queue(), ^(void) {
334 [view topwin];
335 });
338 - (void)topwin {
339 [self.win makeKeyAndOrderFront:nil];
340 [NSApp activateIgnoringOtherApps:YES];
343 // rpc_setlabel updates the client window's label.
344 // If label == nil, the call is a no-op.
345 // Called from an RPC thread with no client lock held.
346 static void
347 rpc_setlabel(Client *client, char *label)
349 DrawView *view = (__bridge DrawView*)client->view;
350 dispatch_sync(dispatch_get_main_queue(), ^(void){
351 [view setlabel:label];
352 });
355 - (void)setlabel:(char*)label {
356 LOG(@"setlabel(%s)", label);
357 if(label == nil)
358 return;
360 @autoreleasepool{
361 NSString *s = [[NSString alloc] initWithUTF8String:label];
362 [self.win setTitle:s];
363 if(client0)
364 [[NSApp dockTile] setBadgeLabel:s];
368 // rpc_setcursor updates the client window's cursor image.
369 // Either c and c2 are both non-nil, or they are both nil to use the default arrow.
370 // Called from an RPC thread with no client lock held.
371 static void
372 rpc_setcursor(Client *client, Cursor *c, Cursor2 *c2)
374 DrawView *view = (__bridge DrawView*)client->view;
375 dispatch_sync(dispatch_get_main_queue(), ^(void){
376 [view setcursor:c cursor2:c2];
377 });
380 - (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2 {
381 if(!c) {
382 c = &bigarrow;
383 c2 = &bigarrow2;
386 NSBitmapImageRep *r, *r2;
387 NSImage *i;
388 NSPoint p;
389 uchar *plane[5], *plane2[5];
390 uint b;
392 r = [[NSBitmapImageRep alloc]
393 initWithBitmapDataPlanes:nil
394 pixelsWide:16
395 pixelsHigh:16
396 bitsPerSample:1
397 samplesPerPixel:2
398 hasAlpha:YES
399 isPlanar:YES
400 colorSpaceName:NSDeviceWhiteColorSpace
401 bytesPerRow:2
402 bitsPerPixel:0];
403 [r getBitmapDataPlanes:plane];
404 for(b=0; b<nelem(c->set); b++){
405 plane[0][b] = ~c->set[b] & c->clr[b];
406 plane[1][b] = c->set[b] | c->clr[b];
409 r2 = [[NSBitmapImageRep alloc]
410 initWithBitmapDataPlanes:nil
411 pixelsWide:32
412 pixelsHigh:32
413 bitsPerSample:1
414 samplesPerPixel:2
415 hasAlpha:YES
416 isPlanar:YES
417 colorSpaceName:NSDeviceWhiteColorSpace
418 bytesPerRow:4
419 bitsPerPixel:0];
420 [r2 getBitmapDataPlanes:plane2];
421 for(b=0; b<nelem(c2->set); b++){
422 plane2[0][b] = ~c2->set[b] & c2->clr[b];
423 plane2[1][b] = c2->set[b] | c2->clr[b];
426 static BOOL debug = NO;
427 if(debug){
428 NSData *data = [r representationUsingType: NSBitmapImageFileTypeBMP properties: @{}];
429 [data writeToFile: @"/tmp/r.bmp" atomically: NO];
430 data = [r2 representationUsingType: NSBitmapImageFileTypeBMP properties: @{}];
431 [data writeToFile: @"/tmp/r2.bmp" atomically: NO];
432 debug = NO;
435 i = [[NSImage alloc] initWithSize:NSMakeSize(16, 16)];
436 [i addRepresentation:r2];
437 [i addRepresentation:r];
439 p = NSMakePoint(-c->offset.x, -c->offset.y);
440 self.currentCursor = [[NSCursor alloc] initWithImage:i hotSpot:p];
441 [self.win invalidateCursorRectsForView:self];
444 - (void)initimg {
445 @autoreleasepool {
446 CGFloat scale;
447 NSSize size;
448 MTLTextureDescriptor *textureDesc;
450 size = [self convertSizeToBacking:[self bounds].size];
451 self.client->mouserect = Rect(0, 0, size.width, size.height);
453 LOG(@"initimg %.0f %.0f", size.width, size.height);
455 self.img = allocmemimage(self.client->mouserect, XRGB32);
456 if(self.img == nil)
457 panic("allocmemimage: %r");
458 if(self.img->data == nil)
459 panic("img->data == nil");
461 textureDesc = [MTLTextureDescriptor
462 texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
463 width:size.width
464 height:size.height
465 mipmapped:NO];
466 textureDesc.allowGPUOptimizedContents = YES;
467 textureDesc.usage = MTLTextureUsageShaderRead;
468 textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
469 self.dlayer.texture = [self.dlayer.device newTextureWithDescriptor:textureDesc];
471 scale = [self.win backingScaleFactor];
472 [self.dlayer setDrawableSize:size];
473 [self.dlayer setContentsScale:scale];
475 // NOTE: This is not really the display DPI.
476 // On retina, scale is 2; otherwise it is 1.
477 // This formula gives us 220 for retina, 110 otherwise.
478 // That's not quite right but it's close to correct.
479 // https://en.wikipedia.org/wiki/Retina_display#Models
480 self.client->displaydpi = scale * 110;
484 // rpc_flush flushes changes to view.img's rectangle r
485 // to the on-screen window, making them visible.
486 // Called from an RPC thread with no client lock held.
487 static void
488 rpc_flush(Client *client, Rectangle r)
490 DrawView *view = (__bridge DrawView*)client->view;
491 dispatch_async(dispatch_get_main_queue(), ^(void){
492 [view flush:r];
493 });
496 - (void)flush:(Rectangle)r {
497 @autoreleasepool{
498 if(!rectclip(&r, Rect(0, 0, self.dlayer.texture.width, self.dlayer.texture.height)) || !rectclip(&r, self.img->r))
499 return;
501 // drawlk protects the pixel data in self.img.
502 // In addition to avoiding a technical data race,
503 // the lock avoids drawing partial updates, which makes
504 // animations like sweeping windows much less flickery.
505 qlock(&drawlk);
506 [self.dlayer.texture
507 replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r))
508 mipmapLevel:0
509 withBytes:byteaddr(self.img, Pt(r.min.x, r.min.y))
510 bytesPerRow:self.img->width*sizeof(u32int)];
511 qunlock(&drawlk);
513 NSRect nr = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
514 dispatch_time_t time;
516 LOG(@"callsetNeedsDisplayInRect(%g, %g, %g, %g)", nr.origin.x, nr.origin.y, nr.size.width, nr.size.height);
517 nr = [self.win convertRectFromBacking:nr];
518 LOG(@"setNeedsDisplayInRect(%g, %g, %g, %g)", nr.origin.x, nr.origin.y, nr.size.width, nr.size.height);
519 [self.dlayer setNeedsDisplayInRect:nr];
521 time = dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC);
522 dispatch_after(time, dispatch_get_main_queue(), ^(void){
523 [self.dlayer setNeedsDisplayInRect:nr];
524 });
526 [self enlargeLastInputRect:nr];
530 // rpc_resizeimg forces the client window to discard its current window and make a new one.
531 // It is called when the user types Cmd-R to toggle whether retina mode is forced.
532 // Called from an RPC thread with no client lock held.
533 static void
534 rpc_resizeimg(Client *c)
536 DrawView *view = (__bridge DrawView*)c->view;
537 dispatch_async(dispatch_get_main_queue(), ^(void){
538 [view resizeimg];
539 });
542 - (void)resizeimg {
543 [self initimg];
544 gfx_replacescreenimage(self.client, self.img);
547 - (void)windowDidResize:(NSNotification *)notification {
548 if(![self inLiveResize] && self.img) {
549 [self resizeimg];
552 - (void)viewDidEndLiveResize
554 [super viewDidEndLiveResize];
555 if(self.img)
556 [self resizeimg];
559 - (void)viewDidChangeBackingProperties
561 [super viewDidChangeBackingProperties];
562 if(self.img)
563 [self resizeimg];
566 // rpc_resizewindow asks for the client window to be resized to size r.
567 // Called from an RPC thread with no client lock held.
568 static void
569 rpc_resizewindow(Client *c, Rectangle r)
571 DrawView *view = (__bridge DrawView*)c->view;
573 LOG(@"resizewindow %d %d %d %d", r.min.x, r.min.y, Dx(r), Dy(r));
574 dispatch_async(dispatch_get_main_queue(), ^(void){
575 NSSize s;
577 s = [view convertSizeFromBacking:NSMakeSize(Dx(r), Dy(r))];
578 [view.win setContentSize:s];
579 });
583 - (void)windowDidBecomeKey:(id)arg {
584 [self sendmouse:0];
587 - (void)windowDidResignKey:(id)arg {
588 gfx_abortcompose(self.client);
591 - (void)mouseMoved:(NSEvent*)e{ [self getmouse:e];}
592 - (void)mouseDown:(NSEvent*)e{ [self getmouse:e];}
593 - (void)mouseDragged:(NSEvent*)e{ [self getmouse:e];}
594 - (void)mouseUp:(NSEvent*)e{ [self getmouse:e];}
595 - (void)otherMouseDown:(NSEvent*)e{ [self getmouse:e];}
596 - (void)otherMouseDragged:(NSEvent*)e{ [self getmouse:e];}
597 - (void)otherMouseUp:(NSEvent*)e{ [self getmouse:e];}
598 - (void)rightMouseDown:(NSEvent*)e{ [self getmouse:e];}
599 - (void)rightMouseDragged:(NSEvent*)e{ [self getmouse:e];}
600 - (void)rightMouseUp:(NSEvent*)e{ [self getmouse:e];}
602 - (void)scrollWheel:(NSEvent*)e
604 NSInteger s;
606 s = [e scrollingDeltaY];
607 if(s > 0)
608 [self sendmouse:8];
609 else if (s < 0)
610 [self sendmouse:16];
613 - (void)keyDown:(NSEvent*)e
615 LOG(@"keyDown to interpret");
617 [self interpretKeyEvents:[NSArray arrayWithObject:e]];
619 [self resetLastInputRect];
622 - (void)flagsChanged:(NSEvent*)e
624 static NSEventModifierFlags omod;
625 NSEventModifierFlags m;
626 uint b;
628 LOG(@"flagsChanged");
629 m = [e modifierFlags];
631 b = [NSEvent pressedMouseButtons];
632 b = (b&~6) | (b&4)>>1 | (b&2)<<1;
633 if(b){
634 if(m & ~omod & NSEventModifierFlagControl)
635 b |= 1;
636 if(m & ~omod & NSEventModifierFlagOption)
637 b |= 2;
638 if(m & ~omod & NSEventModifierFlagCommand)
639 b |= 4;
640 [self sendmouse:b];
641 }else if(m & ~omod & NSEventModifierFlagOption)
642 gfx_keystroke(self.client, Kalt);
644 omod = m;
647 - (void)magnifyWithEvent:(NSEvent*)e
649 if(fabs([e magnification]) > 0.02)
650 [[self window] toggleFullScreen:nil];
653 - (void)touchesBeganWithEvent:(NSEvent*)e
655 _tapping = YES;
656 _tapFingers = [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count;
657 _tapTime = msec();
659 - (void)touchesMovedWithEvent:(NSEvent*)e
661 _tapping = NO;
663 - (void)touchesEndedWithEvent:(NSEvent*)e
665 if(_tapping
666 && [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count == 0
667 && msec() - _tapTime < 250){
668 switch(_tapFingers){
669 case 3:
670 [self sendmouse:2];
671 [self sendmouse:0];
672 break;
673 case 4:
674 [self sendmouse:2];
675 [self sendmouse:1];
676 [self sendmouse:0];
677 break;
679 _tapping = NO;
682 - (void)touchesCancelledWithEvent:(NSEvent*)e
684 _tapping = NO;
687 - (void)getmouse:(NSEvent *)e
689 NSUInteger b;
690 NSEventModifierFlags m;
692 b = [NSEvent pressedMouseButtons];
693 b = b&~6 | (b&4)>>1 | (b&2)<<1;
694 b = mouseswap(b);
696 if(b == 1){
697 m = [e modifierFlags];
698 if(m & NSEventModifierFlagOption){
699 gfx_abortcompose(self.client);
700 b = 2;
701 }else
702 if(m & NSEventModifierFlagCommand)
703 b = 4;
705 [self sendmouse:b];
708 - (void)sendmouse:(NSUInteger)b
710 NSPoint p;
712 p = [self.window convertPointToBacking:
713 [self.window mouseLocationOutsideOfEventStream]];
714 p.y = Dy(self.client->mouserect) - p.y;
715 // LOG(@"(%g, %g) <- sendmouse(%d)", p.x, p.y, (uint)b);
716 gfx_mousetrack(self.client, p.x, p.y, b, msec());
717 if(b && _lastInputRect.size.width && _lastInputRect.size.height)
718 [self resetLastInputRect];
721 // rpc_setmouse moves the mouse cursor.
722 // Called from an RPC thread with no client lock held.
723 static void
724 rpc_setmouse(Client *c, Point p)
726 DrawView *view = (__bridge DrawView*)c->view;
727 dispatch_async(dispatch_get_main_queue(), ^(void){
728 [view setmouse:p];
729 });
732 - (void)setmouse:(Point)p {
733 @autoreleasepool{
734 NSPoint q;
736 LOG(@"setmouse(%d,%d)", p.x, p.y);
737 q = [self.win convertPointFromBacking:NSMakePoint(p.x, p.y)];
738 LOG(@"(%g, %g) <- fromBacking", q.x, q.y);
739 q = [self convertPoint:q toView:nil];
740 LOG(@"(%g, %g) <- toWindow", q.x, q.y);
741 q = [self.win convertPointToScreen:q];
742 LOG(@"(%g, %g) <- toScreen", q.x, q.y);
743 // Quartz has the origin of the "global display
744 // coordinate space" at the top left of the primary
745 // screen with y increasing downward, while Cocoa has
746 // the origin at the bottom left of the primary screen
747 // with y increasing upward. We flip the coordinate
748 // with a negative sign and shift upward by the height
749 // of the primary screen.
750 q.y = NSScreen.screens[0].frame.size.height - q.y;
751 LOG(@"(%g, %g) <- setmouse", q.x, q.y);
752 CGWarpMouseCursorPosition(NSPointToCGPoint(q));
753 CGAssociateMouseAndMouseCursorPosition(true);
758 - (void)resetCursorRects {
759 [super resetCursorRects];
760 [self addCursorRect:self.bounds cursor:self.currentCursor];
763 // conforms to protocol NSTextInputClient
764 - (BOOL)hasMarkedText { return _markedRange.location != NSNotFound; }
765 - (NSRange)markedRange { return _markedRange; }
766 - (NSRange)selectedRange { return _selectedRange; }
768 - (void)setMarkedText:(id)string
769 selectedRange:(NSRange)sRange
770 replacementRange:(NSRange)rRange
772 NSString *str;
774 LOG(@"setMarkedText: %@ (%ld, %ld) (%ld, %ld)", string,
775 sRange.location, sRange.length,
776 rRange.location, rRange.length);
778 [self clearInput];
780 if([string isKindOfClass:[NSAttributedString class]])
781 str = [string string];
782 else
783 str = string;
785 if(rRange.location == NSNotFound){
786 if(_markedRange.location != NSNotFound){
787 rRange = _markedRange;
788 }else{
789 rRange = _selectedRange;
793 if(str.length == 0){
794 [_tmpText deleteCharactersInRange:rRange];
795 [self unmarkText];
796 }else{
797 _markedRange = NSMakeRange(rRange.location, str.length);
798 [_tmpText replaceCharactersInRange:rRange withString:str];
800 _selectedRange.location = rRange.location + sRange.location;
801 _selectedRange.length = sRange.length;
803 if(_tmpText.length){
804 uint i;
805 LOG(@"text length %ld", _tmpText.length);
806 for(i = 0; i <= _tmpText.length; ++i){
807 if(i == _markedRange.location)
808 gfx_keystroke(self.client, '[');
809 if(_selectedRange.length){
810 if(i == _selectedRange.location)
811 gfx_keystroke(self.client, '{');
812 if(i == NSMaxRange(_selectedRange))
813 gfx_keystroke(self.client, '}');
815 if(i == NSMaxRange(_markedRange))
816 gfx_keystroke(self.client, ']');
817 if(i < _tmpText.length)
818 gfx_keystroke(self.client, [_tmpText characterAtIndex:i]);
820 int l;
821 l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
822 + (_selectedRange.length > 0);
823 LOG(@"move left %d", l);
824 for(i = 0; i < l; ++i)
825 gfx_keystroke(self.client, Kleft);
828 LOG(@"text: \"%@\" (%ld,%ld) (%ld,%ld)", _tmpText,
829 _markedRange.location, _markedRange.length,
830 _selectedRange.location, _selectedRange.length);
833 - (void)unmarkText {
834 //NSUInteger i;
835 NSUInteger len;
837 LOG(@"unmarkText");
838 len = [_tmpText length];
839 //for(i = 0; i < len; ++i)
840 // gfx_keystroke(self.client, [_tmpText characterAtIndex:i]);
841 [_tmpText deleteCharactersInRange:NSMakeRange(0, len)];
842 _markedRange = NSMakeRange(NSNotFound, 0);
843 _selectedRange = NSMakeRange(0, 0);
846 - (NSArray<NSAttributedStringKey>*)validAttributesForMarkedText {
847 LOG(@"validAttributesForMarkedText");
848 return @[];
851 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)r
852 actualRange:(NSRangePointer)actualRange
854 NSRange sr;
855 NSAttributedString *s;
857 LOG(@"attributedSubstringForProposedRange: (%ld, %ld) (%ld, %ld)",
858 r.location, r.length, actualRange->location, actualRange->length);
859 sr = NSMakeRange(0, [_tmpText length]);
860 sr = NSIntersectionRange(sr, r);
861 if(actualRange)
862 *actualRange = sr;
863 LOG(@"use range: %ld, %ld", sr.location, sr.length);
864 s = nil;
865 if(sr.length)
866 s = [[NSAttributedString alloc]
867 initWithString:[_tmpText substringWithRange:sr]];
868 LOG(@" return %@", s);
869 return s;
872 - (void)insertText:(id)s replacementRange:(NSRange)r {
873 NSUInteger i;
874 NSUInteger len;
876 LOG(@"insertText: %@ replacementRange: %ld, %ld", s, r.location, r.length);
878 [self clearInput];
880 len = [s length];
881 for(i = 0; i < len; ++i)
882 gfx_keystroke(self.client, [s characterAtIndex:i]);
883 [_tmpText deleteCharactersInRange:NSMakeRange(0, _tmpText.length)];
884 _markedRange = NSMakeRange(NSNotFound, 0);
885 _selectedRange = NSMakeRange(0, 0);
888 - (NSUInteger)characterIndexForPoint:(NSPoint)point
890 LOG(@"characterIndexForPoint: %g, %g", point.x, point.y);
891 return 0;
894 - (NSRect)firstRectForCharacterRange:(NSRange)r actualRange:(NSRangePointer)actualRange {
895 LOG(@"firstRectForCharacterRange: (%ld, %ld) (%ld, %ld)",
896 r.location, r.length, actualRange->location, actualRange->length);
897 if(actualRange)
898 *actualRange = r;
899 return [[self window] convertRectToScreen:_lastInputRect];
902 - (void)doCommandBySelector:(SEL)s {
903 NSEvent *e;
904 NSEventModifierFlags m;
905 uint c, k;
907 LOG(@"doCommandBySelector (%@)", NSStringFromSelector(s));
909 e = [NSApp currentEvent];
910 c = [[e characters] characterAtIndex:0];
911 k = keycvt(c);
912 LOG(@"keyDown: character0: 0x%x -> 0x%x", c, k);
913 m = [e modifierFlags];
915 if(m & NSEventModifierFlagCommand){
916 if((m & NSEventModifierFlagShift) && 'a' <= k && k <= 'z')
917 k += 'A' - 'a';
918 if(' '<=k && k<='~')
919 k += Kcmd;
921 if(k>0)
922 gfx_keystroke(self.client, k);
925 // Helper for managing input rect approximately
926 - (void)resetLastInputRect {
927 LOG(@"resetLastInputRect");
928 _lastInputRect.origin.x = 0.0;
929 _lastInputRect.origin.y = 0.0;
930 _lastInputRect.size.width = 0.0;
931 _lastInputRect.size.height = 0.0;
934 - (void)enlargeLastInputRect:(NSRect)r {
935 r.origin.y = [self bounds].size.height - r.origin.y - r.size.height;
936 _lastInputRect = NSUnionRect(_lastInputRect, r);
937 LOG(@"update last input rect (%g, %g, %g, %g)",
938 _lastInputRect.origin.x, _lastInputRect.origin.y,
939 _lastInputRect.size.width, _lastInputRect.size.height);
942 - (void)clearInput {
943 if(_tmpText.length){
944 uint i;
945 int l;
946 l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
947 + (_selectedRange.length > 0);
948 LOG(@"move right %d", l);
949 for(i = 0; i < l; ++i)
950 gfx_keystroke(self.client, Kright);
951 l = _tmpText.length+2+2*(_selectedRange.length > 0);
952 LOG(@"backspace %d", l);
953 for(uint i = 0; i < l; ++i)
954 gfx_keystroke(self.client, Kbs);
958 - (NSApplicationPresentationOptions)window:(id)arg
959 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
960 // The default for full-screen is to auto-hide the dock and menu bar,
961 // but the menu bar in particular comes back when the cursor is just
962 // near the top of the screen, which makes acme's top tag line very difficult to use.
963 // Disable the menu bar entirely.
964 // In theory this code disables the dock entirely too, but if you drag the mouse
965 // down far enough off the bottom of the screen the dock still unhides.
966 // That's OK.
967 NSApplicationPresentationOptions o;
968 o = proposedOptions;
969 o &= ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar);
970 o |= NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
971 return o;
974 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
975 // This is a heavier-weight way to make sure the menu bar and dock go away,
976 // but this affects all screens even though the app is running on full screen
977 // on only one screen, so it's not great. The behavior from the
978 // willUseFullScreenPresentationOptions seems to be enough for now.
979 /*
980 [[NSApplication sharedApplication]
981 setPresentationOptions:NSApplicationPresentationHideMenuBar | NSApplicationPresentationHideDock];
982 */
985 - (void)windowDidExitFullScreen:(NSNotification*)notification {
986 /*
987 [[NSApplication sharedApplication]
988 setPresentationOptions:NSApplicationPresentationDefault];
989 */
991 @end
993 static uint
994 msec(void)
996 return nsec()/1000000;
999 static uint
1000 keycvt(uint code)
1002 switch(code){
1003 case '\r': return '\n';
1004 case 127: return '\b';
1005 case NSUpArrowFunctionKey: return Kup;
1006 case NSDownArrowFunctionKey: return Kdown;
1007 case NSLeftArrowFunctionKey: return Kleft;
1008 case NSRightArrowFunctionKey: return Kright;
1009 case NSInsertFunctionKey: return Kins;
1010 case NSDeleteFunctionKey: return Kdel;
1011 case NSHomeFunctionKey: return Khome;
1012 case NSEndFunctionKey: return Kend;
1013 case NSPageUpFunctionKey: return Kpgup;
1014 case NSPageDownFunctionKey: return Kpgdown;
1015 case NSF1FunctionKey: return KF|1;
1016 case NSF2FunctionKey: return KF|2;
1017 case NSF3FunctionKey: return KF|3;
1018 case NSF4FunctionKey: return KF|4;
1019 case NSF5FunctionKey: return KF|5;
1020 case NSF6FunctionKey: return KF|6;
1021 case NSF7FunctionKey: return KF|7;
1022 case NSF8FunctionKey: return KF|8;
1023 case NSF9FunctionKey: return KF|9;
1024 case NSF10FunctionKey: return KF|10;
1025 case NSF11FunctionKey: return KF|11;
1026 case NSF12FunctionKey: return KF|12;
1027 case NSBeginFunctionKey:
1028 case NSPrintScreenFunctionKey:
1029 case NSScrollLockFunctionKey:
1030 case NSF13FunctionKey:
1031 case NSF14FunctionKey:
1032 case NSF15FunctionKey:
1033 case NSF16FunctionKey:
1034 case NSF17FunctionKey:
1035 case NSF18FunctionKey:
1036 case NSF19FunctionKey:
1037 case NSF20FunctionKey:
1038 case NSF21FunctionKey:
1039 case NSF22FunctionKey:
1040 case NSF23FunctionKey:
1041 case NSF24FunctionKey:
1042 case NSF25FunctionKey:
1043 case NSF26FunctionKey:
1044 case NSF27FunctionKey:
1045 case NSF28FunctionKey:
1046 case NSF29FunctionKey:
1047 case NSF30FunctionKey:
1048 case NSF31FunctionKey:
1049 case NSF32FunctionKey:
1050 case NSF33FunctionKey:
1051 case NSF34FunctionKey:
1052 case NSF35FunctionKey:
1053 case NSPauseFunctionKey:
1054 case NSSysReqFunctionKey:
1055 case NSBreakFunctionKey:
1056 case NSResetFunctionKey:
1057 case NSStopFunctionKey:
1058 case NSMenuFunctionKey:
1059 case NSUserFunctionKey:
1060 case NSSystemFunctionKey:
1061 case NSPrintFunctionKey:
1062 case NSClearLineFunctionKey:
1063 case NSClearDisplayFunctionKey:
1064 case NSInsertLineFunctionKey:
1065 case NSDeleteLineFunctionKey:
1066 case NSInsertCharFunctionKey:
1067 case NSDeleteCharFunctionKey:
1068 case NSPrevFunctionKey:
1069 case NSNextFunctionKey:
1070 case NSSelectFunctionKey:
1071 case NSExecuteFunctionKey:
1072 case NSUndoFunctionKey:
1073 case NSRedoFunctionKey:
1074 case NSFindFunctionKey:
1075 case NSHelpFunctionKey:
1076 case NSModeSwitchFunctionKey: return 0;
1077 default: return code;
1081 // rpc_getsnarf reads the current pasteboard as a plain text string.
1082 // Called from an RPC thread with no client lock held.
1083 char*
1084 rpc_getsnarf(void)
1086 char __block *ret;
1088 ret = nil;
1089 dispatch_sync(dispatch_get_main_queue(), ^(void) {
1090 @autoreleasepool {
1091 NSPasteboard *pb = [NSPasteboard generalPasteboard];
1092 NSString *s = [pb stringForType:NSPasteboardTypeString];
1093 if(s)
1094 ret = strdup((char*)[s UTF8String]);
1096 });
1097 return ret;
1100 // rpc_putsnarf writes the given text to the pasteboard.
1101 // Called from an RPC thread with no client lock held.
1102 void
1103 rpc_putsnarf(char *s)
1105 if(s == nil || strlen(s) >= SnarfSize)
1106 return;
1108 dispatch_sync(dispatch_get_main_queue(), ^(void) {
1109 @autoreleasepool{
1110 NSArray *t = [NSArray arrayWithObject:NSPasteboardTypeString];
1111 NSPasteboard *pb = [NSPasteboard generalPasteboard];
1112 NSString *str = [[NSString alloc] initWithUTF8String:s];
1113 [pb declareTypes:t owner:nil];
1114 [pb setString:str forType:NSPasteboardTypeString];
1116 });
1119 // rpc_bouncemouse is for sending a mouse event
1120 // back to the X11 window manager rio(1).
1121 // Does not apply here.
1122 static void
1123 rpc_bouncemouse(Client *c, Mouse m)
1127 // We don't use the graphics thread state during memimagedraw,
1128 // so rpc_gfxdrawlock and rpc_gfxdrawunlock are no-ops.
1129 void
1130 rpc_gfxdrawlock(void)
1134 void
1135 rpc_gfxdrawunlock(void)
1139 static void
1140 setprocname(const char *s)
1142 CFStringRef process_name;
1144 process_name = CFStringCreateWithBytes(nil, (uchar*)s, strlen(s), kCFStringEncodingUTF8, false);
1146 // Adapted from Chrome's mac_util.mm.
1147 // http://src.chromium.org/viewvc/chrome/trunk/src/base/mac/mac_util.mm
1149 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
1151 // Redistribution and use in source and binary forms, with or without
1152 // modification, are permitted provided that the following conditions are
1153 // met:
1155 // * Redistributions of source code must retain the above copyright
1156 // notice, this list of conditions and the following disclaimer.
1157 // * Redistributions in binary form must reproduce the above
1158 // copyright notice, this list of conditions and the following disclaimer
1159 // in the documentation and/or other materials provided with the
1160 // distribution.
1161 // * Neither the name of Google Inc. nor the names of its
1162 // contributors may be used to endorse or promote products derived from
1163 // this software without specific prior written permission.
1165 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1166 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1167 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1168 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1169 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
1170 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
1171 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1172 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
1173 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1174 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1175 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1176 // Warning: here be dragons! This is SPI reverse-engineered from WebKit's
1177 // plugin host, and could break at any time (although realistically it's only
1178 // likely to break in a new major release).
1179 // When 10.7 is available, check that this still works, and update this
1180 // comment for 10.8.
1182 // Private CFType used in these LaunchServices calls.
1183 typedef CFTypeRef PrivateLSASN;
1184 typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
1185 typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
1186 CFStringRef,
1187 CFStringRef,
1188 CFDictionaryRef*);
1190 static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
1191 NULL;
1192 static LSSetApplicationInformationItemType
1193 ls_set_application_information_item_func = NULL;
1194 static CFStringRef ls_display_name_key = NULL;
1196 static bool did_symbol_lookup = false;
1197 if (!did_symbol_lookup) {
1198 did_symbol_lookup = true;
1199 CFBundleRef launch_services_bundle =
1200 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
1201 if (!launch_services_bundle) {
1202 fprint(2, "Failed to look up LaunchServices bundle\n");
1203 return;
1206 ls_get_current_application_asn_func =
1207 (LSGetCurrentApplicationASNType)(
1208 CFBundleGetFunctionPointerForName(
1209 launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
1210 if (!ls_get_current_application_asn_func)
1211 fprint(2, "Could not find _LSGetCurrentApplicationASN\n");
1213 ls_set_application_information_item_func =
1214 (LSSetApplicationInformationItemType)(
1215 CFBundleGetFunctionPointerForName(
1216 launch_services_bundle,
1217 CFSTR("_LSSetApplicationInformationItem")));
1218 if (!ls_set_application_information_item_func)
1219 fprint(2, "Could not find _LSSetApplicationInformationItem\n");
1221 CFStringRef* key_pointer = (CFStringRef*)(
1222 CFBundleGetDataPointerForName(launch_services_bundle,
1223 CFSTR("_kLSDisplayNameKey")));
1224 ls_display_name_key = key_pointer ? *key_pointer : NULL;
1225 if (!ls_display_name_key)
1226 fprint(2, "Could not find _kLSDisplayNameKey\n");
1228 // Internally, this call relies on the Mach ports that are started up by the
1229 // Carbon Process Manager. In debug builds this usually happens due to how
1230 // the logging layers are started up; but in release, it isn't started in as
1231 // much of a defined order. So if the symbols had to be loaded, go ahead
1232 // and force a call to make sure the manager has been initialized and hence
1233 // the ports are opened.
1234 ProcessSerialNumber psn;
1235 GetCurrentProcess(&psn);
1237 if (!ls_get_current_application_asn_func ||
1238 !ls_set_application_information_item_func ||
1239 !ls_display_name_key) {
1240 return;
1243 PrivateLSASN asn = ls_get_current_application_asn_func();
1244 // Constant used by WebKit; what exactly it means is unknown.
1245 const int magic_session_constant = -2;
1246 OSErr err =
1247 ls_set_application_information_item_func(magic_session_constant, asn,
1248 ls_display_name_key,
1249 process_name,
1250 NULL /* optional out param */);
1251 if(err != noErr)
1252 fprint(2, "Call to set process name failed\n");