This patch reverts the recent patch named "devdraw: fix for OS X 10.8", and fixes redrawing on OSX 10.8 differently, making scrolling under Acme 3 times faster. R=rsc, mirtchovski CC=plan9port.codebot http://codereview.appspot.com/6452087
1336 lines
25 KiB
Objective-C
1336 lines
25 KiB
Objective-C
/*
|
|
* Cocoa's event loop must be in main thread.
|
|
*
|
|
* Unless otherwise stated, all coordinate systems
|
|
* are bottom-left-based.
|
|
*/
|
|
|
|
#define Cursor OSXCursor
|
|
#define Point OSXPoint
|
|
#define Rect OSXRect
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#undef Cursor
|
|
#undef Point
|
|
#undef Rect
|
|
|
|
#include <u.h>
|
|
#include <libc.h>
|
|
#include "cocoa-thread.h"
|
|
#include <draw.h>
|
|
#include <memdraw.h>
|
|
#include <keyboard.h>
|
|
#include <cursor.h>
|
|
#include "cocoa-screen.h"
|
|
#include "osx-keycodes.h"
|
|
#include "devdraw.h"
|
|
#include "bigarrow.h"
|
|
#include "glendapng.h"
|
|
|
|
AUTOFRAMEWORK(Cocoa)
|
|
|
|
#define LOG if(0)NSLog
|
|
#define panic sysfatal
|
|
|
|
int usegestures = 0;
|
|
int useliveresizing = 0;
|
|
int useoldfullscreen = 0;
|
|
int usebigarrow = 0;
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: devdraw (don't run directly)\n");
|
|
threadexitsall("usage");
|
|
}
|
|
|
|
@interface appdelegate : NSObject @end
|
|
|
|
void
|
|
threadmain(int argc, char **argv)
|
|
{
|
|
/*
|
|
* Move the protocol off stdin/stdout so that
|
|
* any inadvertent prints don't screw things up.
|
|
*/
|
|
dup(0,3);
|
|
dup(1,4);
|
|
close(0);
|
|
close(1);
|
|
open("/dev/null", OREAD);
|
|
open("/dev/null", OWRITE);
|
|
|
|
ARGBEGIN{
|
|
case 'D': /* for good ps -a listings */
|
|
break;
|
|
case 'f':
|
|
useoldfullscreen = 1;
|
|
break;
|
|
case 'g':
|
|
usegestures = 1;
|
|
break;
|
|
case 'b':
|
|
usebigarrow = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
}ARGEND
|
|
|
|
if(OSX_VERSION < 100700)
|
|
[NSAutoreleasePool new];
|
|
|
|
[NSApplication sharedApplication];
|
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
[NSApp setDelegate:[appdelegate new]];
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
[NSApp run];
|
|
}
|
|
|
|
#define WIN win.ofs[win.isofs]
|
|
|
|
struct
|
|
{
|
|
NSWindow *ofs[2]; /* ofs[1] for old fullscreen; ofs[0] else */
|
|
int isofs;
|
|
int isnfs;
|
|
NSView *content;
|
|
NSBitmapImageRep *img;
|
|
int needimg;
|
|
int deferflush;
|
|
NSCursor *cursor;
|
|
} win;
|
|
|
|
struct
|
|
{
|
|
NSCursor *bigarrow;
|
|
int kbuttons;
|
|
int mbuttons;
|
|
NSPoint mpos;
|
|
int mscroll;
|
|
int willactivate;
|
|
} in;
|
|
|
|
static void hidebars(int);
|
|
static void flushimg(NSRect);
|
|
static void autoflushwin(int);
|
|
static void flushwin(void);
|
|
static void followzoombutton(NSRect);
|
|
static void getmousepos(void);
|
|
static void makeicon(void);
|
|
static void makemenu(void);
|
|
static void makewin(char*);
|
|
static void sendmouse(void);
|
|
static void setcursor0(Cursor*);
|
|
static void togglefs(void);
|
|
static void acceptresizing(int);
|
|
|
|
static NSCursor* makecursor(Cursor*);
|
|
|
|
@implementation appdelegate
|
|
- (void)applicationDidFinishLaunching:(id)arg
|
|
{
|
|
in.bigarrow = makecursor(&bigarrow);
|
|
makeicon();
|
|
makemenu();
|
|
[NSApplication
|
|
detachDrawingThread:@selector(callservep9p:)
|
|
toTarget:[self class] withObject:nil];
|
|
}
|
|
- (void)windowDidBecomeKey:(id)arg
|
|
{
|
|
getmousepos();
|
|
sendmouse();
|
|
}
|
|
- (void)windowDidResize:(id)arg
|
|
{
|
|
getmousepos();
|
|
sendmouse();
|
|
}
|
|
- (void)windowWillStartLiveResize:(id)arg
|
|
{
|
|
if(useliveresizing == 0)
|
|
[win.content setHidden:YES];
|
|
}
|
|
- (void)windowDidEndLiveResize:(id)arg
|
|
{
|
|
if(useliveresizing == 0)
|
|
[win.content setHidden:NO];
|
|
}
|
|
- (void)windowDidChangeScreen:(id)arg
|
|
{
|
|
if(win.isnfs || win.isofs)
|
|
hidebars(1);
|
|
[win.ofs[1] setFrame:[[WIN screen] frame] display:YES];
|
|
}
|
|
- (BOOL)windowShouldZoom:(id)arg toFrame:(NSRect)r
|
|
{
|
|
followzoombutton(r);
|
|
return YES;
|
|
}
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(id)arg
|
|
{
|
|
return YES;
|
|
}
|
|
- (void)applicationDidBecomeActive:(id)arg{ in.willactivate = 0;}
|
|
- (void)windowWillEnterFullScreen:(id)arg{ acceptresizing(1);}
|
|
- (void)windowDidEnterFullScreen:(id)arg{ win.isnfs = 1; hidebars(1);}
|
|
- (void)windowWillExitFullScreen:(id)arg{ win.isnfs = 0; hidebars(0);}
|
|
- (void)windowDidExitFullScreen:(id)arg
|
|
{
|
|
NSButton *b;
|
|
|
|
b = [WIN standardWindowButton:NSWindowMiniaturizeButton];
|
|
|
|
if([b isEnabled] == 0){
|
|
[b setEnabled:YES];
|
|
hidebars(0);
|
|
}
|
|
}
|
|
- (void)windowWillClose:(id)arg
|
|
{
|
|
autoflushwin(0); /* can crash otherwise */
|
|
}
|
|
|
|
+ (void)callservep9p:(id)arg
|
|
{
|
|
servep9p();
|
|
[NSApp terminate:self];
|
|
}
|
|
- (void)plumbmanual:(id)arg
|
|
{
|
|
if(fork() != 0)
|
|
return;
|
|
execl("plumb", "plumb", "devdraw(1)", nil);
|
|
}
|
|
+ (void)callflushwin:(id)arg{ flushwin();}
|
|
- (void)calltogglefs:(id)arg{ togglefs();}
|
|
|
|
+ (void)callflushimg:(NSValue*)v{ flushimg([v rectValue]);}
|
|
+ (void)callmakewin:(NSValue*)v{ makewin([v pointerValue]);}
|
|
+ (void)callsetcursor0:(NSValue*)v{ setcursor0([v pointerValue]);}
|
|
@end
|
|
|
|
static Memimage* initimg(void);
|
|
|
|
Memimage*
|
|
attachscreen(char *label, char *winsize)
|
|
{
|
|
static int first = 1;
|
|
|
|
if(first)
|
|
first = 0;
|
|
else
|
|
panic("attachscreen called twice");
|
|
|
|
if(label == nil)
|
|
label = "gnot a label";
|
|
if(strcmp(label, "page") == 0)
|
|
useliveresizing = 1;
|
|
|
|
/*
|
|
* Create window in main thread, else no cursor
|
|
* change while resizing.
|
|
*/
|
|
[appdelegate
|
|
performSelectorOnMainThread:@selector(callmakewin:)
|
|
withObject:[NSValue valueWithPointer:winsize]
|
|
waitUntilDone:YES];
|
|
// makewin(winsize);
|
|
|
|
kicklabel(label);
|
|
return initimg();
|
|
}
|
|
|
|
@interface appwin : NSWindow @end
|
|
@interface contentview : NSView @end
|
|
|
|
@implementation appwin
|
|
- (NSTimeInterval)animationResizeTime:(NSRect)r
|
|
{
|
|
return 0;
|
|
}
|
|
- (BOOL)canBecomeKeyWindow
|
|
{
|
|
return YES; /* else no keyboard for old fullscreen */
|
|
}
|
|
- (void)makeKeyAndOrderFront:(id)arg
|
|
{
|
|
LOG(@"makeKeyAndOrderFront");
|
|
|
|
autoflushwin(1);
|
|
[win.content setHidden:NO];
|
|
[super makeKeyAndOrderFront:arg];
|
|
}
|
|
- (void)miniaturize:(id)arg
|
|
{
|
|
[super miniaturize:arg];
|
|
[NSApp hide:nil];
|
|
|
|
[win.content setHidden:YES];
|
|
autoflushwin(0);
|
|
}
|
|
- (void)deminiaturize:(id)arg
|
|
{
|
|
autoflushwin(1);
|
|
[win.content setHidden:NO];
|
|
[super deminiaturize:arg];
|
|
}
|
|
@end
|
|
|
|
double
|
|
min(double a, double b)
|
|
{
|
|
return a<b? a : b;
|
|
}
|
|
|
|
enum
|
|
{
|
|
Winstyle = NSTitledWindowMask
|
|
| NSClosableWindowMask
|
|
| NSMiniaturizableWindowMask
|
|
| NSResizableWindowMask
|
|
};
|
|
|
|
static void
|
|
makewin(char *s)
|
|
{
|
|
NSRect r, sr;
|
|
NSWindow *w;
|
|
Rectangle wr;
|
|
int i, set;
|
|
|
|
sr = [[NSScreen mainScreen] frame];
|
|
r = [[NSScreen mainScreen] visibleFrame];
|
|
|
|
if(s && *s){
|
|
if(parsewinsize(s, &wr, &set) < 0)
|
|
sysfatal("%r");
|
|
}else{
|
|
wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
|
|
set = 0;
|
|
}
|
|
|
|
r.origin.x = wr.min.x;
|
|
r.origin.y = sr.size.height-wr.max.y; /* winsize is top-left-based */
|
|
r.size.width = min(Dx(wr), r.size.width);
|
|
r.size.height = min(Dy(wr), r.size.height);
|
|
r = [NSWindow contentRectForFrameRect:r
|
|
styleMask:Winstyle];
|
|
|
|
w = [[appwin alloc]
|
|
initWithContentRect:r
|
|
styleMask:Winstyle
|
|
backing:NSBackingStoreBuffered defer:NO];
|
|
if(!set)
|
|
[w center];
|
|
#if OSX_VERSION >= 100700
|
|
[w setCollectionBehavior:
|
|
NSWindowCollectionBehaviorFullScreenPrimary];
|
|
#endif
|
|
[w setContentMinSize:NSMakeSize(128,128)];
|
|
|
|
win.ofs[0] = w;
|
|
win.ofs[1] = [[appwin alloc]
|
|
initWithContentRect:sr
|
|
styleMask:NSBorderlessWindowMask
|
|
backing:NSBackingStoreBuffered defer:YES];
|
|
for(i=0; i<2; i++){
|
|
[win.ofs[i] setAcceptsMouseMovedEvents:YES];
|
|
[win.ofs[i] setDelegate:[NSApp delegate]];
|
|
[win.ofs[i] setDisplaysWhenScreenProfileChanges:NO];
|
|
}
|
|
win.isofs = 0;
|
|
win.content = [contentview new];
|
|
[WIN setContentView:win.content];
|
|
}
|
|
|
|
static Memimage*
|
|
initimg(void)
|
|
{
|
|
Memimage *i;
|
|
NSSize size;
|
|
Rectangle r;
|
|
|
|
size = [win.content bounds].size;
|
|
LOG(@"initimg %.0f %.0f", size.width, size.height);
|
|
|
|
r = Rect(0, 0, size.width, size.height);
|
|
i = allocmemimage(r, XBGR32);
|
|
if(i == nil)
|
|
panic("allocmemimage: %r");
|
|
if(i->data == nil)
|
|
panic("i->data == nil");
|
|
|
|
win.img = [[NSBitmapImageRep alloc]
|
|
initWithBitmapDataPlanes:&i->data->bdata
|
|
pixelsWide:Dx(r)
|
|
pixelsHigh:Dy(r)
|
|
bitsPerSample:8
|
|
samplesPerPixel:3
|
|
hasAlpha:NO
|
|
isPlanar:NO
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
|
bytesPerRow:bytesperline(r, 32)
|
|
bitsPerPixel:32];
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
resizeimg()
|
|
{
|
|
[win.img release];
|
|
_drawreplacescreenimage(initimg());
|
|
|
|
mouseresized = 1;
|
|
sendmouse();
|
|
}
|
|
|
|
static void
|
|
waitimg(int msec)
|
|
{
|
|
NSDate *limit;
|
|
int n;
|
|
|
|
win.needimg = 1;
|
|
win.deferflush = 0;
|
|
|
|
n = 0;
|
|
limit = [NSDate dateWithTimeIntervalSinceNow:msec/1000.0];
|
|
do{
|
|
[[NSRunLoop currentRunLoop]
|
|
runMode:@"waiting image"
|
|
beforeDate:limit];
|
|
n++;
|
|
}while(win.needimg && [(NSDate*)[NSDate date] compare:limit]<0);
|
|
|
|
win.deferflush = win.needimg;
|
|
|
|
LOG(@"waitimg %s (%d loop)", win.needimg?"defer":"ok", n);
|
|
}
|
|
|
|
void
|
|
_flushmemscreen(Rectangle r)
|
|
{
|
|
static int n;
|
|
NSRect rect;
|
|
|
|
LOG(@"_flushmemscreen");
|
|
|
|
if(n==0){
|
|
n++;
|
|
return; /* to skip useless white init rect */
|
|
}else
|
|
if(n==1){
|
|
[WIN performSelectorOnMainThread:
|
|
@selector(makeKeyAndOrderFront:)
|
|
withObject:nil
|
|
waitUntilDone:NO];
|
|
n++;
|
|
}else
|
|
if([win.content canDraw] == 0)
|
|
return;
|
|
|
|
rect = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
|
|
[appdelegate
|
|
performSelectorOnMainThread:@selector(callflushimg:)
|
|
withObject:[NSValue valueWithRect:rect]
|
|
waitUntilDone:YES
|
|
modes:[NSArray arrayWithObjects:
|
|
NSRunLoopCommonModes,
|
|
@"waiting image", nil]];
|
|
}
|
|
|
|
static void drawimg(NSRect, uint);
|
|
static void drawresizehandle(void);
|
|
|
|
enum
|
|
{
|
|
Pixel = 1,
|
|
Barsize = 4*Pixel,
|
|
Cornersize = 3*Pixel,
|
|
Handlesize = 3*Barsize + 1*Pixel,
|
|
};
|
|
|
|
static void
|
|
flushimg(NSRect rect)
|
|
{
|
|
NSRect dr, r;
|
|
|
|
if([win.content lockFocusIfCanDraw] == 0)
|
|
return;
|
|
|
|
if(win.needimg){
|
|
if(!NSEqualSizes(rect.size, [win.img size])){
|
|
LOG(@"flushimg reject %.0f %.0f",
|
|
rect.size.width, rect.size.height);
|
|
[win.content unlockFocus];
|
|
return;
|
|
}
|
|
win.needimg = 0;
|
|
}else
|
|
win.deferflush = 1;
|
|
|
|
LOG(@"flushimg ok %.0f %.0f", rect.size.width, rect.size.height);
|
|
|
|
/*
|
|
* Unless we are inside "drawRect", we have to round
|
|
* the corners ourselves, if this is the custom.
|
|
* "NSCompositeSourceIn" can do that, but we don't
|
|
* apply it to the whole rectangle, because this
|
|
* slows down trackpad scrolling considerably in
|
|
* Acme.
|
|
*/
|
|
r = [win.content bounds];
|
|
r.size.height -= Cornersize;
|
|
dr = NSIntersectionRect(r, rect);
|
|
drawimg(dr, NSCompositeCopy);
|
|
|
|
r.origin.y = r.size.height;
|
|
r.size = NSMakeSize(Cornersize, Cornersize);
|
|
dr = NSIntersectionRect(r, rect);
|
|
drawimg(dr, NSCompositeSourceIn);
|
|
|
|
r.origin.x = [win.img size].width - Cornersize;
|
|
dr = NSIntersectionRect(r, rect);
|
|
drawimg(dr, NSCompositeSourceIn);
|
|
|
|
r.size.width = r.origin.x - Cornersize;
|
|
r.origin.x -= r.size.width;
|
|
dr = NSIntersectionRect(r, rect);
|
|
drawimg(dr, NSCompositeCopy);
|
|
|
|
if(OSX_VERSION<100700 && win.isofs==0){
|
|
r.origin.x = [win.img size].width - Handlesize;
|
|
r.origin.y = [win.img size].height - Handlesize;
|
|
r.size = NSMakeSize(Handlesize, Handlesize);
|
|
if(NSIntersectsRect(r, rect))
|
|
drawresizehandle();
|
|
}
|
|
[win.content unlockFocus];
|
|
}
|
|
|
|
static void
|
|
autoflushwin(int set)
|
|
{
|
|
static NSTimer *t;
|
|
|
|
if(set){
|
|
if(t)
|
|
return;
|
|
/*
|
|
* We need "NSRunLoopCommonModes", otherwise the
|
|
* timer will not fire during live resizing.
|
|
*/
|
|
t = [NSTimer
|
|
timerWithTimeInterval:0.033
|
|
target:[appdelegate class]
|
|
selector:@selector(callflushwin:) userInfo:nil
|
|
repeats:YES];
|
|
[[NSRunLoop currentRunLoop] addTimer:t
|
|
forMode:NSRunLoopCommonModes];
|
|
}else{
|
|
[t invalidate];
|
|
t = nil;
|
|
win.deferflush = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
flushwin(void)
|
|
{
|
|
if(win.deferflush && win.needimg==0){
|
|
[WIN flushWindow];
|
|
win.deferflush = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
drawimg(NSRect dr, uint op)
|
|
{
|
|
CGContextRef c;
|
|
CGImageRef i;
|
|
NSRect sr;
|
|
|
|
if(NSIsEmptyRect(dr))
|
|
return;
|
|
|
|
sr = [win.content convertRect:dr fromView:nil];
|
|
|
|
if(OSX_VERSION >= 100800){
|
|
i = CGImageCreateWithImageInRect([win.img CGImage], NSRectToCGRect(dr));
|
|
c = [[WIN graphicsContext] graphicsPort];
|
|
|
|
CGContextSaveGState(c);
|
|
if(op == NSCompositeSourceIn)
|
|
CGContextSetBlendMode(c, kCGBlendModeSourceIn);
|
|
CGContextTranslateCTM(c, 0, [win.img size].height);
|
|
CGContextScaleCTM(c, 1, -1);
|
|
CGContextDrawImage(c, NSRectToCGRect(sr), i);
|
|
CGContextRestoreGState(c);
|
|
|
|
CGImageRelease(i);
|
|
}else{
|
|
[win.img drawInRect:dr fromRect:sr
|
|
operation:op fraction:1
|
|
respectFlipped:YES hints:nil];
|
|
}
|
|
// NSFrameRect(dr);
|
|
}
|
|
|
|
static void
|
|
drawresizehandle(void)
|
|
{
|
|
NSColor *color[Barsize];
|
|
NSPoint a,b;
|
|
Point c;
|
|
int i,j;
|
|
|
|
c = Pt([win.img size].width, [win.img size].height);
|
|
|
|
[[WIN graphicsContext] setShouldAntialias:NO];
|
|
|
|
color[0] = [NSColor clearColor];
|
|
color[1] = [NSColor darkGrayColor];
|
|
color[2] = [NSColor lightGrayColor];
|
|
color[3] = [NSColor whiteColor];
|
|
|
|
for(i=1; i+Barsize <= Handlesize; )
|
|
for(j=0; j<Barsize; j++){
|
|
[color[j] setStroke];
|
|
i++;
|
|
a = NSMakePoint(c.x-i, c.y-1);
|
|
b = NSMakePoint(c.x-2, c.y+1-i);
|
|
[NSBezierPath strokeLineFromPoint:a toPoint:b];
|
|
}
|
|
}
|
|
|
|
static void getgesture(NSEvent*);
|
|
static void getkeyboard(NSEvent*);
|
|
static void getmouse(NSEvent*);
|
|
static void gettouch(NSEvent*, int);
|
|
static void updatecursor(void);
|
|
|
|
@implementation contentview
|
|
/*
|
|
* "drawRect" is called each time Cocoa needs an
|
|
* image, and each time we call "display". It is
|
|
* preceded by background painting, and followed by
|
|
* "flushWindow".
|
|
*/
|
|
- (void)drawRect:(NSRect)r
|
|
{
|
|
static int first = 1;
|
|
|
|
LOG(@"drawrect %.0f %.0f %.0f %.0f",
|
|
r.origin.x, r.origin.y, r.size.width, r.size.height);
|
|
|
|
if(first)
|
|
first = 0;
|
|
else
|
|
resizeimg();
|
|
|
|
if([WIN inLiveResize])
|
|
waitimg(100);
|
|
else
|
|
waitimg(500);
|
|
}
|
|
- (BOOL)isFlipped
|
|
{
|
|
return YES; /* to make the content's origin top left */
|
|
}
|
|
- (BOOL)acceptsFirstResponder
|
|
{
|
|
return YES; /* else no keyboard */
|
|
}
|
|
- (id)initWithFrame:(NSRect)r
|
|
{
|
|
[super initWithFrame:r];
|
|
[self setAcceptsTouchEvents:YES];
|
|
[self setHidden:YES]; /* to avoid early "drawRect" call */
|
|
return self;
|
|
}
|
|
- (void)setHidden:(BOOL)set
|
|
{
|
|
if(!set)
|
|
[WIN makeFirstResponder:self]; /* for keyboard focus */
|
|
[super setHidden:set];
|
|
}
|
|
- (void)cursorUpdate:(NSEvent*)e{ updatecursor();}
|
|
|
|
- (void)mouseMoved:(NSEvent*)e{ getmouse(e);}
|
|
- (void)mouseDown:(NSEvent*)e{ getmouse(e);}
|
|
- (void)mouseDragged:(NSEvent*)e{ getmouse(e);}
|
|
- (void)mouseUp:(NSEvent*)e{ getmouse(e);}
|
|
- (void)otherMouseDown:(NSEvent*)e{ getmouse(e);}
|
|
- (void)otherMouseDragged:(NSEvent*)e{ getmouse(e);}
|
|
- (void)otherMouseUp:(NSEvent*)e{ getmouse(e);}
|
|
- (void)rightMouseDown:(NSEvent*)e{ getmouse(e);}
|
|
- (void)rightMouseDragged:(NSEvent*)e{ getmouse(e);}
|
|
- (void)rightMouseUp:(NSEvent*)e{ getmouse(e);}
|
|
- (void)scrollWheel:(NSEvent*)e{ getmouse(e);}
|
|
|
|
- (void)keyDown:(NSEvent*)e{ getkeyboard(e);}
|
|
- (void)flagsChanged:(NSEvent*)e{ getkeyboard(e);}
|
|
|
|
- (void)magnifyWithEvent:(NSEvent*)e{ getgesture(e);}
|
|
|
|
- (void)touchesBeganWithEvent:(NSEvent*)e
|
|
{
|
|
gettouch(e, NSTouchPhaseBegan);
|
|
}
|
|
- (void)touchesMovedWithEvent:(NSEvent*)e
|
|
{
|
|
gettouch(e, NSTouchPhaseMoved);
|
|
}
|
|
- (void)touchesEndedWithEvent:(NSEvent*)e
|
|
{
|
|
gettouch(e, NSTouchPhaseEnded);
|
|
}
|
|
- (void)touchesCancelledWithEvent:(NSEvent*)e
|
|
{
|
|
gettouch(e, NSTouchPhaseCancelled);
|
|
}
|
|
@end
|
|
|
|
static int keycvt[] =
|
|
{
|
|
[QZ_IBOOK_ENTER] '\n',
|
|
[QZ_RETURN] '\n',
|
|
[QZ_ESCAPE] 27,
|
|
[QZ_BACKSPACE] '\b',
|
|
[QZ_LALT] Kalt,
|
|
[QZ_LCTRL] Kctl,
|
|
[QZ_LSHIFT] Kshift,
|
|
[QZ_F1] KF+1,
|
|
[QZ_F2] KF+2,
|
|
[QZ_F3] KF+3,
|
|
[QZ_F4] KF+4,
|
|
[QZ_F5] KF+5,
|
|
[QZ_F6] KF+6,
|
|
[QZ_F7] KF+7,
|
|
[QZ_F8] KF+8,
|
|
[QZ_F9] KF+9,
|
|
[QZ_F10] KF+10,
|
|
[QZ_F11] KF+11,
|
|
[QZ_F12] KF+12,
|
|
[QZ_INSERT] Kins,
|
|
[QZ_DELETE] 0x7F,
|
|
[QZ_HOME] Khome,
|
|
[QZ_END] Kend,
|
|
[QZ_KP_PLUS] '+',
|
|
[QZ_KP_MINUS] '-',
|
|
[QZ_TAB] '\t',
|
|
[QZ_PAGEUP] Kpgup,
|
|
[QZ_PAGEDOWN] Kpgdown,
|
|
[QZ_UP] Kup,
|
|
[QZ_DOWN] Kdown,
|
|
[QZ_LEFT] Kleft,
|
|
[QZ_RIGHT] Kright,
|
|
[QZ_KP_MULTIPLY] '*',
|
|
[QZ_KP_DIVIDE] '/',
|
|
[QZ_KP_ENTER] '\n',
|
|
[QZ_KP_PERIOD] '.',
|
|
[QZ_KP0] '0',
|
|
[QZ_KP1] '1',
|
|
[QZ_KP2] '2',
|
|
[QZ_KP3] '3',
|
|
[QZ_KP4] '4',
|
|
[QZ_KP5] '5',
|
|
[QZ_KP6] '6',
|
|
[QZ_KP7] '7',
|
|
[QZ_KP8] '8',
|
|
[QZ_KP9] '9',
|
|
};
|
|
|
|
@interface apptext : NSTextView @end
|
|
|
|
@implementation apptext
|
|
- (void)doCommandBySelector:(SEL)s{} /* Esc key beeps otherwise */
|
|
- (void)insertText:(id)arg{} /* to avoid a latency after some time */
|
|
@end
|
|
|
|
static void
|
|
interpretdeadkey(NSEvent *e)
|
|
{
|
|
static apptext *t;
|
|
|
|
if(t == nil)
|
|
t = [apptext new];
|
|
[t interpretKeyEvents:[NSArray arrayWithObject:e]];
|
|
}
|
|
|
|
static void
|
|
getkeyboard(NSEvent *e)
|
|
{
|
|
static int omod;
|
|
NSString *s;
|
|
char c;
|
|
int k, m;
|
|
uint code;
|
|
|
|
m = [e modifierFlags];
|
|
|
|
switch([e type]){
|
|
case NSKeyDown:
|
|
s = [e characters];
|
|
c = [s UTF8String][0];
|
|
|
|
interpretdeadkey(e);
|
|
|
|
if(m & NSCommandKeyMask){
|
|
if(' '<=c && c<='~')
|
|
keystroke(Kcmd+c);
|
|
break;
|
|
}
|
|
k = c;
|
|
code = [e keyCode];
|
|
if(code<nelem(keycvt) && keycvt[code])
|
|
k = keycvt[code];
|
|
if(k==0)
|
|
break;
|
|
if(k>0)
|
|
keystroke(k);
|
|
else
|
|
keystroke([s characterAtIndex:0]);
|
|
break;
|
|
|
|
case NSFlagsChanged:
|
|
if(in.mbuttons || in.kbuttons){
|
|
in.kbuttons = 0;
|
|
if(m & NSAlternateKeyMask)
|
|
in.kbuttons |= 2;
|
|
if(m & NSCommandKeyMask)
|
|
in.kbuttons |= 4;
|
|
sendmouse();
|
|
}else
|
|
if(m&NSAlternateKeyMask && (omod&NSAlternateKeyMask)==0)
|
|
keystroke(Kalt);
|
|
break;
|
|
|
|
default:
|
|
panic("getkey: unexpected event type");
|
|
}
|
|
omod = m;
|
|
}
|
|
|
|
/*
|
|
* Devdraw does not use NSTrackingArea, that often
|
|
* forgets to update the cursor on entering and on
|
|
* leaving the area, and that sometimes stops sending
|
|
* us MouseMove events, at least on OS X Lion.
|
|
*/
|
|
static void
|
|
updatecursor(void)
|
|
{
|
|
NSCursor *c;
|
|
int isdown, isinside;
|
|
|
|
isinside = NSPointInRect(in.mpos, [win.content bounds]);
|
|
isdown = (in.mbuttons || in.kbuttons);
|
|
|
|
if(win.cursor && (isinside || isdown))
|
|
c = win.cursor;
|
|
else if(isinside && usebigarrow)
|
|
c = in.bigarrow;
|
|
else
|
|
c = [NSCursor arrowCursor];
|
|
[c set];
|
|
|
|
/*
|
|
* Without this trick, we can come back from the dock
|
|
* with a resize cursor.
|
|
*/
|
|
if(OSX_VERSION >= 100700)
|
|
[NSCursor unhide];
|
|
}
|
|
|
|
static void
|
|
acceptresizing(int set)
|
|
{
|
|
uint old, style;
|
|
|
|
old = [WIN styleMask];
|
|
|
|
if((old | NSResizableWindowMask) != Winstyle)
|
|
return; /* when entering new fullscreen */
|
|
|
|
if(set)
|
|
style = Winstyle;
|
|
else
|
|
style = Winstyle & ~NSResizableWindowMask;
|
|
|
|
if(style != old)
|
|
[WIN setStyleMask:style];
|
|
}
|
|
|
|
static void
|
|
getmousepos(void)
|
|
{
|
|
NSPoint p, q;
|
|
|
|
p = [WIN mouseLocationOutsideOfEventStream];
|
|
q = [win.content convertPoint:p fromView:nil];
|
|
in.mpos.x = round(q.x);
|
|
in.mpos.y = round(q.y);
|
|
|
|
updatecursor();
|
|
|
|
if(win.isnfs || win.isofs)
|
|
hidebars(1);
|
|
else if(OSX_VERSION>=100700 && [WIN inLiveResize]==0){
|
|
if(p.x<12 && p.y<12 && p.x>2 && p.y>2)
|
|
acceptresizing(0);
|
|
else
|
|
acceptresizing(1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
getmouse(NSEvent *e)
|
|
{
|
|
float d;
|
|
int b, m;
|
|
|
|
if([WIN isKeyWindow] == 0)
|
|
return;
|
|
|
|
getmousepos();
|
|
|
|
switch([e type]){
|
|
case NSLeftMouseDown:
|
|
case NSLeftMouseUp:
|
|
case NSOtherMouseDown:
|
|
case NSOtherMouseUp:
|
|
case NSRightMouseDown:
|
|
case NSRightMouseUp:
|
|
b = [NSEvent pressedMouseButtons];
|
|
b = b&~6 | (b&4)>>1 | (b&2)<<1;
|
|
b = mouseswap(b);
|
|
|
|
if(b == 1){
|
|
m = [e modifierFlags];
|
|
if(m & NSAlternateKeyMask){
|
|
abortcompose();
|
|
b = 2;
|
|
}else
|
|
if(m & NSCommandKeyMask)
|
|
b = 4;
|
|
}
|
|
in.mbuttons = b;
|
|
break;
|
|
|
|
case NSScrollWheel:
|
|
#if OSX_VERSION >= 100700
|
|
d = [e scrollingDeltaY];
|
|
#else
|
|
d = [e deltaY];
|
|
#endif
|
|
if(d>0)
|
|
in.mscroll = 8;
|
|
else
|
|
if(d<0)
|
|
in.mscroll = 16;
|
|
break;
|
|
|
|
case NSMouseMoved:
|
|
case NSLeftMouseDragged:
|
|
case NSRightMouseDragged:
|
|
case NSOtherMouseDragged:
|
|
break;
|
|
|
|
default:
|
|
panic("getmouse: unexpected event type");
|
|
}
|
|
sendmouse();
|
|
}
|
|
|
|
#define Minpinch 0.02
|
|
|
|
static void
|
|
getgesture(NSEvent *e)
|
|
{
|
|
switch([e type]){
|
|
case NSEventTypeMagnify:
|
|
if(fabs([e magnification]) > Minpinch)
|
|
togglefs();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void sendclick(int);
|
|
|
|
static uint
|
|
msec(void)
|
|
{
|
|
return nsec()/1000000;
|
|
}
|
|
|
|
static void
|
|
gettouch(NSEvent *e, int type)
|
|
{
|
|
static int tapping;
|
|
static uint taptime;
|
|
NSSet *set;
|
|
int p;
|
|
|
|
switch(type){
|
|
case NSTouchPhaseBegan:
|
|
p = NSTouchPhaseTouching;
|
|
set = [e touchesMatchingPhase:p inView:nil];
|
|
if(set.count == 3){
|
|
tapping = 1;
|
|
taptime = msec();
|
|
}else
|
|
if(set.count > 3)
|
|
tapping = 0;
|
|
break;
|
|
|
|
case NSTouchPhaseMoved:
|
|
tapping = 0;
|
|
break;
|
|
|
|
case NSTouchPhaseEnded:
|
|
p = NSTouchPhaseTouching;
|
|
set = [e touchesMatchingPhase:p inView:nil];
|
|
if(set.count == 0){
|
|
if(tapping && msec()-taptime<400)
|
|
sendclick(2);
|
|
tapping = 0;
|
|
}
|
|
break;
|
|
|
|
case NSTouchPhaseCancelled:
|
|
break;
|
|
|
|
default:
|
|
panic("gettouch: unexpected event type");
|
|
}
|
|
}
|
|
|
|
static void
|
|
sendclick(int b)
|
|
{
|
|
in.mbuttons = b;
|
|
sendmouse();
|
|
in.mbuttons = 0;
|
|
sendmouse();
|
|
}
|
|
|
|
static void
|
|
sendmouse(void)
|
|
{
|
|
NSSize size;
|
|
int b;
|
|
|
|
size = [win.content bounds].size;
|
|
mouserect = Rect(0, 0, size.width, size.height);
|
|
|
|
b = in.kbuttons | in.mbuttons | in.mscroll;
|
|
mousetrack(in.mpos.x, in.mpos.y, b, msec());
|
|
in.mscroll = 0;
|
|
}
|
|
|
|
void
|
|
setmouse(Point p)
|
|
{
|
|
static int first = 1;
|
|
NSPoint q;
|
|
NSRect r;
|
|
|
|
if([NSApp isActive]==0 && in.willactivate==0)
|
|
return;
|
|
|
|
if(first){
|
|
/* Try to move Acme's scrollbars without that! */
|
|
CGSetLocalEventsSuppressionInterval(0);
|
|
first = 0;
|
|
}
|
|
if([WIN inLiveResize])
|
|
return;
|
|
|
|
in.mpos = NSMakePoint(p.x, p.y); // race condition
|
|
|
|
q = [win.content convertPoint:in.mpos toView:nil];
|
|
q = [WIN convertBaseToScreen:q];
|
|
|
|
r = [[[NSScreen screens] objectAtIndex:0] frame];
|
|
q.y = r.size.height - q.y; /* Quartz is top-left-based here */
|
|
|
|
CGWarpMouseCursorPosition(NSPointToCGPoint(q));
|
|
}
|
|
|
|
static void
|
|
followzoombutton(NSRect r)
|
|
{
|
|
NSRect wr;
|
|
Point p;
|
|
|
|
wr = [WIN frame];
|
|
wr.origin.y += wr.size.height;
|
|
r.origin.y += r.size.height;
|
|
|
|
getmousepos();
|
|
p.x = (r.origin.x - wr.origin.x) + in.mpos.x;
|
|
p.y = -(r.origin.y - wr.origin.y) + in.mpos.y;
|
|
setmouse(p);
|
|
}
|
|
|
|
static void
|
|
togglefs(void)
|
|
{
|
|
uint opt, tmp;
|
|
|
|
#if OSX_VERSION >= 100700
|
|
NSScreen *s, *s0;
|
|
|
|
s = [WIN screen];
|
|
s0 = [[NSScreen screens] objectAtIndex:0];
|
|
|
|
if((s==s0 && useoldfullscreen==0) || win.isnfs) {
|
|
[WIN toggleFullScreen:nil];
|
|
return;
|
|
}
|
|
#endif
|
|
[win.content retain];
|
|
[WIN orderOut:nil];
|
|
[WIN setContentView:nil];
|
|
|
|
win.isofs = ! win.isofs;
|
|
hidebars(win.isofs);
|
|
|
|
/*
|
|
* If we move the window from one space to another,
|
|
* ofs[0] and ofs[1] can be on different spaces.
|
|
* This "setCollectionBehavior" trick moves the
|
|
* window to the active space.
|
|
*/
|
|
opt = [WIN collectionBehavior];
|
|
tmp = opt | NSWindowCollectionBehaviorCanJoinAllSpaces;
|
|
[WIN setContentView:win.content];
|
|
[WIN setCollectionBehavior:tmp];
|
|
[WIN makeKeyAndOrderFront:nil];
|
|
[WIN setCollectionBehavior:opt];
|
|
[win.content release];
|
|
}
|
|
|
|
enum
|
|
{
|
|
Autohiddenbars = NSApplicationPresentationAutoHideDock
|
|
| NSApplicationPresentationAutoHideMenuBar,
|
|
|
|
Hiddenbars = NSApplicationPresentationHideDock
|
|
| NSApplicationPresentationHideMenuBar,
|
|
};
|
|
|
|
static void
|
|
hidebars(int set)
|
|
{
|
|
NSScreen *s,*s0;
|
|
uint old, opt;
|
|
|
|
s = [WIN screen];
|
|
s0 = [[NSScreen screens] objectAtIndex:0];
|
|
old = [NSApp presentationOptions];
|
|
|
|
#if OSX_VERSION >= 100700
|
|
/* This bit can get lost, resulting in dreadful bugs. */
|
|
if(win.isnfs)
|
|
old |= NSApplicationPresentationFullScreen;
|
|
#endif
|
|
|
|
if(set && s==s0)
|
|
opt = (old & ~Autohiddenbars) | Hiddenbars;
|
|
else
|
|
opt = old & ~(Autohiddenbars | Hiddenbars);
|
|
|
|
if(opt != old)
|
|
[NSApp setPresentationOptions:opt];
|
|
}
|
|
|
|
static void
|
|
makemenu(void)
|
|
{
|
|
NSMenu *m;
|
|
NSMenuItem *i0,*i1;
|
|
|
|
m = [NSMenu new];
|
|
i0 = [m addItemWithTitle:@"app" action:NULL keyEquivalent:@""];
|
|
i1 = [m addItemWithTitle:@"help" action:NULL keyEquivalent:@""];
|
|
[NSApp setMainMenu:m];
|
|
[m release];
|
|
|
|
m = [[NSMenu alloc] initWithTitle:@"app"];
|
|
[m addItemWithTitle:@"Full Screen"
|
|
action:@selector(calltogglefs:)
|
|
keyEquivalent:@"f"];
|
|
[m addItemWithTitle:@"Hide"
|
|
action:@selector(hide:)
|
|
keyEquivalent:@"h"];
|
|
[m addItemWithTitle:@"Quit"
|
|
action:@selector(terminate:)
|
|
keyEquivalent:@"q"];
|
|
[i0 setSubmenu:m];
|
|
[m release];
|
|
|
|
m = [[NSMenu alloc] initWithTitle:@"help"];
|
|
[m addItemWithTitle:@"Plumb devdraw(1)"
|
|
action:@selector(plumbmanual:)
|
|
keyEquivalent:@""];
|
|
[i1 setSubmenu:m];
|
|
[m release];
|
|
}
|
|
|
|
static void
|
|
makeicon(void)
|
|
{
|
|
NSData *d;
|
|
NSImage *i;
|
|
|
|
d = [[NSData alloc]
|
|
initWithBytes:glenda_png
|
|
length:(sizeof glenda_png)];
|
|
|
|
i = [[NSImage alloc] initWithData:d];
|
|
[NSApp setApplicationIconImage:i];
|
|
[[NSApp dockTile] display];
|
|
[i release];
|
|
[d release];
|
|
}
|
|
|
|
QLock snarfl;
|
|
|
|
char*
|
|
getsnarf(void)
|
|
{
|
|
NSPasteboard *pb;
|
|
NSString *s;
|
|
|
|
pb = [NSPasteboard generalPasteboard];
|
|
|
|
qlock(&snarfl);
|
|
s = [pb stringForType:NSPasteboardTypeString];
|
|
qunlock(&snarfl);
|
|
|
|
if(s)
|
|
return strdup((char*)[s UTF8String]);
|
|
else
|
|
return nil;
|
|
}
|
|
|
|
void
|
|
putsnarf(char *s)
|
|
{
|
|
NSArray *t;
|
|
NSPasteboard *pb;
|
|
NSString *str;
|
|
|
|
if(strlen(s) >= SnarfSize)
|
|
return;
|
|
|
|
t = [NSArray arrayWithObject:NSPasteboardTypeString];
|
|
pb = [NSPasteboard generalPasteboard];
|
|
str = [[NSString alloc] initWithUTF8String:s];
|
|
|
|
qlock(&snarfl);
|
|
[pb declareTypes:t owner:nil];
|
|
[pb setString:str forType:NSPasteboardTypeString];
|
|
qunlock(&snarfl);
|
|
|
|
[str release];
|
|
}
|
|
|
|
void
|
|
kicklabel(char *label)
|
|
{
|
|
NSString *s;
|
|
|
|
if(label == nil)
|
|
return;
|
|
|
|
s = [[NSString alloc] initWithUTF8String:label];
|
|
[win.ofs[0] setTitle:s];
|
|
[win.ofs[1] setTitle:s];
|
|
[[NSApp dockTile] setBadgeLabel:s];
|
|
[s release];
|
|
}
|
|
|
|
void
|
|
setcursor(Cursor *c)
|
|
{
|
|
/*
|
|
* No cursor change unless in main thread.
|
|
*/
|
|
[appdelegate
|
|
performSelectorOnMainThread:@selector(callsetcursor0:)
|
|
withObject:[NSValue valueWithPointer:c]
|
|
waitUntilDone:YES];
|
|
}
|
|
|
|
static void
|
|
setcursor0(Cursor *c)
|
|
{
|
|
NSCursor *d;
|
|
|
|
d = win.cursor;
|
|
|
|
if(c)
|
|
win.cursor = makecursor(c);
|
|
else
|
|
win.cursor = nil;
|
|
|
|
updatecursor();
|
|
|
|
if(d)
|
|
[d release];
|
|
}
|
|
|
|
static NSCursor*
|
|
makecursor(Cursor *c)
|
|
{
|
|
NSBitmapImageRep *r;
|
|
NSCursor *d;
|
|
NSImage *i;
|
|
NSPoint p;
|
|
int b;
|
|
uchar *plane[5];
|
|
|
|
r = [[NSBitmapImageRep alloc]
|
|
initWithBitmapDataPlanes:nil
|
|
pixelsWide:16
|
|
pixelsHigh:16
|
|
bitsPerSample:1
|
|
samplesPerPixel:2
|
|
hasAlpha:YES
|
|
isPlanar:YES
|
|
colorSpaceName:NSDeviceBlackColorSpace
|
|
bytesPerRow:2
|
|
bitsPerPixel:1];
|
|
|
|
[r getBitmapDataPlanes:plane];
|
|
|
|
for(b=0; b<2*16; b++){
|
|
plane[0][b] = c->set[b];
|
|
plane[1][b] = c->clr[b];
|
|
}
|
|
p = NSMakePoint(-c->offset.x, -c->offset.y);
|
|
i = [NSImage new];
|
|
[i addRepresentation:r];
|
|
[r release];
|
|
|
|
d = [[NSCursor alloc] initWithImage:i hotSpot:p];
|
|
[i release];
|
|
return d;
|
|
}
|
|
|
|
void
|
|
topwin(void)
|
|
{
|
|
[WIN performSelectorOnMainThread:
|
|
@selector(makeKeyAndOrderFront:)
|
|
withObject:nil
|
|
waitUntilDone:NO];
|
|
|
|
in.willactivate = 1;
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
}
|