1 #define Cursor OSXCursor
5 #import <Cocoa/Cocoa.h>
6 #import <Metal/Metal.h>
7 #import <QuartzCore/CAMetalLayer.h>
22 #include <drawfcall.h>
25 #include "glendapng.h"
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 = {
63 @interface AppDelegate : NSObject<NSApplicationDelegate>
66 static AppDelegate *myApp = NULL;
75 [NSApplication sharedApplication];
76 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
77 myApp = [AppDelegate new];
78 [NSApp setDelegate:myApp];
87 [NSApp terminate:myApp];
90 @implementation AppDelegate
91 - (void)applicationDidFinishLaunching:(id)arg
97 LOG(@"applicationDidFinishLaunching");
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"];
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];
116 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
117 return client0 != nil;
121 @interface DrawLayer : CAMetalLayer
122 @property (nonatomic, retain) id<MTLCommandQueue> cmd;
123 @property (nonatomic, retain) id<MTLTexture> texture;
126 @implementation DrawLayer
130 LOG(@"display query drawable");
133 id<CAMetalDrawable> drawable = [self nextDrawable];
135 LOG(@"display couldn't get drawable");
136 [self setNeedsDisplay];
140 LOG(@"display got drawable");
142 id<MTLCommandBuffer> cbuf = [self.cmd commandBuffer];
143 id<MTLBlitCommandEncoder> blit = [cbuf blitCommandEncoder];
144 [blit copyFromTexture:self.texture
147 sourceOrigin:MTLOriginMake(0, 0, 0)
148 sourceSize:MTLSizeMake(self.texture.width, self.texture.height, self.texture.depth)
149 toTexture:drawable.texture
152 destinationOrigin:MTLOriginMake(0, 0, 0)];
155 [cbuf presentDrawable:drawable];
157 [cbuf addCompletedHandler:^(id<MTLCommandBuffer> cmdBuff){
159 NSLog(@"command buffer finished with error: %@",
160 cmdBuff.error.localizedDescription);
162 LOG(@"command buffer finishes present drawable");
166 LOG(@"display commit");
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;
179 - (void)setlabel:(char*)label;
180 - (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2;
181 - (void)setmouse:(Point)p;
183 - (void)getmouse:(NSEvent*)e;
184 - (void)sendmouse:(NSUInteger)b;
185 - (void)resetLastInputRect;
186 - (void)enlargeLastInputRect:(NSRect)r;
189 @implementation DrawView
191 NSMutableString *_tmpText;
192 NSRange _markedRange;
193 NSRange _selectedRange;
194 NSRect _lastInputRect; // The view is flipped, this is not.
196 NSUInteger _tapFingers;
204 [self setAllowedTouchTypes:NSTouchTypeMaskDirect|NSTouchTypeMaskIndirect];
205 _tmpText = [[NSMutableString alloc] initWithCapacity:2];
206 _markedRange = NSMakeRange(NSNotFound, 0);
207 _selectedRange = NSMakeRange(0, 0);
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).
220 rpc_attach(Client *c, char *label, char *winsize)
222 LOG(@"attachscreen(%s, %s)", label, winsize);
225 dispatch_sync(dispatch_get_main_queue(), ^(void) {
227 DrawView *view = [[DrawView new] attach:c winsize:winsize label:label];
231 return ((__bridge DrawView*)c->view).img;
234 - (id)attach:(Client*)client winsize:(char*)winsize label:(char*)label {
241 NSWindowStyleMask Winstyle = NSWindowStyleMaskTitled
242 | NSWindowStyleMaskClosable
243 | NSWindowStyleMaskMiniaturizable
244 | NSWindowStyleMaskResizable;
246 if(label == nil || *label == '\0')
247 Winstyle &= ~NSWindowStyleMaskTitled;
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);
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
268 backing:NSBackingStoreBuffered defer:NO];
269 [win setTitle:@"devdraw"];
273 [win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
274 [win setContentMinSize:NSMakeSize(64,64)];
276 [win setRestorable:NO];
277 [win setAcceptsMouseMovedEvents:YES];
279 client->view = CFBridgingRetain(self);
280 self.client = client;
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]) {
297 device = MTLCreateSystemDefaultDevice();
299 DrawLayer *layer = (DrawLayer*)[self layer];
301 layer.device = device;
302 layer.cmd = [device newCommandQueue];
303 layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
304 layer.framebufferOnly = 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.
312 CALayer *stub = [CALayer layer];
313 stub.frame = CGRectMake(0, 0, 1, 1);
314 [stub setNeedsDisplay];
315 [layer addSublayer:stub];
318 [NSEvent setMouseCoalescingEnabled:NO];
321 [self setlabel:label];
322 [self setcursor:nil cursor2:nil];
327 // rpc_topwin moves the window to the top of the desktop.
328 // Called from an RPC thread with no client lock held.
330 rpc_topwin(Client *c)
332 DrawView *view = (__bridge DrawView*)c->view;
333 dispatch_sync(dispatch_get_main_queue(), ^(void) {
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.
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];
355 - (void)setlabel:(char*)label {
356 LOG(@"setlabel(%s)", label);
361 NSString *s = [[NSString alloc] initWithUTF8String:label];
362 [self.win setTitle:s];
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.
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];
380 - (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2 {
386 NSBitmapImageRep *r, *r2;
389 uchar *plane[5], *plane2[5];
392 r = [[NSBitmapImageRep alloc]
393 initWithBitmapDataPlanes:nil
400 colorSpaceName:NSDeviceWhiteColorSpace
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
417 colorSpaceName:NSDeviceWhiteColorSpace
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;
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];
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];
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);
457 panic("allocmemimage: %r");
458 if(self.img->data == nil)
459 panic("img->data == nil");
461 textureDesc = [MTLTextureDescriptor
462 texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
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.
488 rpc_flush(Client *client, Rectangle r)
490 DrawView *view = (__bridge DrawView*)client->view;
491 dispatch_async(dispatch_get_main_queue(), ^(void){
496 - (void)flush:(Rectangle)r {
498 if(!rectclip(&r, Rect(0, 0, self.dlayer.texture.width, self.dlayer.texture.height)) || !rectclip(&r, self.img->r))
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.
507 replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r))
509 withBytes:byteaddr(self.img, Pt(r.min.x, r.min.y))
510 bytesPerRow:self.img->width*sizeof(u32int)];
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];
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.
534 rpc_resizeimg(Client *c)
536 DrawView *view = (__bridge DrawView*)c->view;
537 dispatch_async(dispatch_get_main_queue(), ^(void){
544 gfx_replacescreenimage(self.client, self.img);
547 - (void)windowDidResize:(NSNotification *)notification {
548 if(![self inLiveResize] && self.img) {
552 - (void)viewDidEndLiveResize
554 [super viewDidEndLiveResize];
559 - (void)viewDidChangeBackingProperties
561 [super viewDidChangeBackingProperties];
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.
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){
577 s = [view convertSizeFromBacking:NSMakeSize(Dx(r), Dy(r))];
578 [view.win setContentSize:s];
583 - (void)windowDidBecomeKey:(id)arg {
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
606 s = [e scrollingDeltaY];
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;
628 LOG(@"flagsChanged");
629 m = [e modifierFlags];
631 b = [NSEvent pressedMouseButtons];
632 b = (b&~6) | (b&4)>>1 | (b&2)<<1;
634 if(m & ~omod & NSEventModifierFlagControl)
636 if(m & ~omod & NSEventModifierFlagOption)
638 if(m & ~omod & NSEventModifierFlagCommand)
641 }else if(m & ~omod & NSEventModifierFlagOption)
642 gfx_keystroke(self.client, Kalt);
647 - (void)magnifyWithEvent:(NSEvent*)e
649 if(fabs([e magnification]) > 0.02)
650 [[self window] toggleFullScreen:nil];
653 - (void)touchesBeganWithEvent:(NSEvent*)e
656 _tapFingers = [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count;
659 - (void)touchesMovedWithEvent:(NSEvent*)e
663 - (void)touchesEndedWithEvent:(NSEvent*)e
666 && [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count == 0
667 && msec() - _tapTime < 250){
682 - (void)touchesCancelledWithEvent:(NSEvent*)e
687 - (void)getmouse:(NSEvent *)e
690 NSEventModifierFlags m;
692 b = [NSEvent pressedMouseButtons];
693 b = b&~6 | (b&4)>>1 | (b&2)<<1;
697 m = [e modifierFlags];
698 if(m & NSEventModifierFlagOption){
699 gfx_abortcompose(self.client);
702 if(m & NSEventModifierFlagCommand)
708 - (void)sendmouse:(NSUInteger)b
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.
724 rpc_setmouse(Client *c, Point p)
726 DrawView *view = (__bridge DrawView*)c->view;
727 dispatch_async(dispatch_get_main_queue(), ^(void){
732 - (void)setmouse:(Point)p {
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
774 LOG(@"setMarkedText: %@ (%ld, %ld) (%ld, %ld)", string,
775 sRange.location, sRange.length,
776 rRange.location, rRange.length);
780 if([string isKindOfClass:[NSAttributedString class]])
781 str = [string string];
785 if(rRange.location == NSNotFound){
786 if(_markedRange.location != NSNotFound){
787 rRange = _markedRange;
789 rRange = _selectedRange;
794 [_tmpText deleteCharactersInRange:rRange];
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;
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]);
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);
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");
851 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)r
852 actualRange:(NSRangePointer)actualRange
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);
863 LOG(@"use range: %ld, %ld", sr.location, sr.length);
866 s = [[NSAttributedString alloc]
867 initWithString:[_tmpText substringWithRange:sr]];
868 LOG(@" return %@", s);
872 - (void)insertText:(id)s replacementRange:(NSRange)r {
876 LOG(@"insertText: %@ replacementRange: %ld, %ld", s, r.location, r.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);
894 - (NSRect)firstRectForCharacterRange:(NSRange)r actualRange:(NSRangePointer)actualRange {
895 LOG(@"firstRectForCharacterRange: (%ld, %ld) (%ld, %ld)",
896 r.location, r.length, actualRange->location, actualRange->length);
899 return [[self window] convertRectToScreen:_lastInputRect];
902 - (void)doCommandBySelector:(SEL)s {
904 NSEventModifierFlags m;
907 LOG(@"doCommandBySelector (%@)", NSStringFromSelector(s));
909 e = [NSApp currentEvent];
910 c = [[e characters] characterAtIndex:0];
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')
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);
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.
967 NSApplicationPresentationOptions o;
969 o &= ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar);
970 o |= NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
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.
980 [[NSApplication sharedApplication]
981 setPresentationOptions:NSApplicationPresentationHideMenuBar | NSApplicationPresentationHideDock];
985 - (void)windowDidExitFullScreen:(NSNotification*)notification {
987 [[NSApplication sharedApplication]
988 setPresentationOptions:NSApplicationPresentationDefault];
996 return nsec()/1000000;
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.
1089 dispatch_sync(dispatch_get_main_queue(), ^(void) {
1091 NSPasteboard *pb = [NSPasteboard generalPasteboard];
1092 NSString *s = [pb stringForType:NSPasteboardTypeString];
1094 ret = strdup((char*)[s UTF8String]);
1100 // rpc_putsnarf writes the given text to the pasteboard.
1101 // Called from an RPC thread with no client lock held.
1103 rpc_putsnarf(char *s)
1105 if(s == nil || strlen(s) >= SnarfSize)
1108 dispatch_sync(dispatch_get_main_queue(), ^(void) {
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];
1119 // rpc_bouncemouse is for sending a mouse event
1120 // back to the X11 window manager rio(1).
1121 // Does not apply here.
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.
1130 rpc_gfxdrawlock(void)
1135 rpc_gfxdrawunlock(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
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
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,
1190 static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
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");
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) {
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;
1247 ls_set_application_information_item_func(magic_session_constant, asn,
1248 ls_display_name_key,
1250 NULL /* optional out param */);
1252 fprint(2, "Call to set process name failed\n");