From 0d7f00c6349a2157cfd39a148d8d6dabd4a9d998 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 11 Dec 2023 15:13:33 -0600 Subject: [PATCH] app: [macOS] use cgo.Handle for referring to Go windows from native code Signed-off-by: Elias Naur --- app/os_macos.go | 101 +++++++++++++++++++++--------------------------- app/os_macos.m | 72 ++++++++++++++++++++-------------- 2 files changed, 87 insertions(+), 86 deletions(-) diff --git a/app/os_macos.go b/app/os_macos.go index e4d7b517..c0864de6 100644 --- a/app/os_macos.go +++ b/app/os_macos.go @@ -10,6 +10,7 @@ import ( "image" "io" "runtime" + "runtime/cgo" "strings" "time" "unicode" @@ -41,6 +42,7 @@ import ( __attribute__ ((visibility ("hidden"))) void gio_main(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight); +__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle); static void writeClipboard(CFTypeRef str) { @autoreleasepool { @@ -264,9 +266,6 @@ type window struct { config Config } -// viewMap is the mapping from Cocoa NSViews to Go windows. -var viewMap = make(map[C.CFTypeRef]*window) - // launched is closed when applicationDidFinishLaunching is called. var launched = make(chan struct{}) @@ -274,18 +273,8 @@ var launched = make(chan struct{}) // cascadeTopLeftFromPoint. var nextTopLeft C.NSPoint -// mustView is like lookupView, except that it panics -// if the view isn't mapped. -func mustView(view C.CFTypeRef) *window { - w, exists := viewMap[view] - if !exists { - panic("no window for view") - } - return w -} - -func insertView(view C.CFTypeRef, w *window) { - viewMap[view] = w +func windowFor(h C.uintptr_t) *window { + return cgo.Handle(h).Value().(*window) } func (w *window) contextView() C.CFTypeRef { @@ -488,14 +477,14 @@ func (w *window) setStage(stage Stage) { } //export gio_onKeys -func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) { +func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) { str := nsstringToString(cstr) kmods := convertMods(mods) ks := key.Release if keyDown { ks = key.Press } - w := mustView(view) + w := windowFor(h) for _, k := range str { if n, ok := convertKey(k); ok { w.ProcessEvent(key.Event{ @@ -508,15 +497,15 @@ func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown } //export gio_onText -func gio_onText(view, cstr C.CFTypeRef) { +func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) { str := nsstringToString(cstr) - w := mustView(view) + w := windowFor(h) w.w.EditorInsert(str) } //export gio_onMouse -func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) { - w := mustView(view) +func gio_onMouse(h C.uintptr_t, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) { + w := windowFor(h) t := time.Duration(float64(ti)*float64(time.Second) + .5) xf, yf := float32(x)*w.scale, float32(y)*w.scale dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale @@ -565,14 +554,14 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, } //export gio_onDraw -func gio_onDraw(view C.CFTypeRef) { - w := mustView(view) +func gio_onDraw(h C.uintptr_t) { + w := windowFor(h) w.draw() } //export gio_onFocus -func gio_onFocus(view C.CFTypeRef, focus C.int) { - w := mustView(view) +func gio_onFocus(h C.uintptr_t, focus C.int) { + w := windowFor(h) w.ProcessEvent(key.FocusEvent{Focus: focus == 1}) if w.stage >= StageInactive { if focus == 0 { @@ -585,15 +574,15 @@ func gio_onFocus(view C.CFTypeRef, focus C.int) { } //export gio_onChangeScreen -func gio_onChangeScreen(view C.CFTypeRef, did uint64) { - w := mustView(view) +func gio_onChangeScreen(h C.uintptr_t, did uint64) { + w := windowFor(h) w.displayLink.SetDisplayID(did) C.setNeedsDisplay(w.view) } //export gio_hasMarkedText -func gio_hasMarkedText(view C.CFTypeRef) C.int { - w := mustView(view) +func gio_hasMarkedText(h C.uintptr_t) C.int { + w := windowFor(h) state := w.w.EditorState() if state.compose.Start != -1 { return 1 @@ -602,8 +591,8 @@ func gio_hasMarkedText(view C.CFTypeRef) C.int { } //export gio_markedRange -func gio_markedRange(view C.CFTypeRef) C.NSRange { - w := mustView(view) +func gio_markedRange(h C.uintptr_t) C.NSRange { + w := windowFor(h) state := w.w.EditorState() rng := state.compose start, end := rng.Start, rng.End @@ -618,8 +607,8 @@ func gio_markedRange(view C.CFTypeRef) C.NSRange { } //export gio_selectedRange -func gio_selectedRange(view C.CFTypeRef) C.NSRange { - w := mustView(view) +func gio_selectedRange(h C.uintptr_t) C.NSRange { + w := windowFor(h) state := w.w.EditorState() rng := state.Selection start, end := rng.Start, rng.End @@ -634,14 +623,14 @@ func gio_selectedRange(view C.CFTypeRef) C.NSRange { } //export gio_unmarkText -func gio_unmarkText(view C.CFTypeRef) { - w := mustView(view) +func gio_unmarkText(h C.uintptr_t) { + w := windowFor(h) w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) } //export gio_setMarkedText -func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) { - w := mustView(view) +func gio_setMarkedText(h C.uintptr_t, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) { + w := windowFor(h) str := nsstringToString(cstr) state := w.w.EditorState() rng := state.compose @@ -680,8 +669,8 @@ func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange } //export gio_substringForProposedRange -func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef { - w := mustView(view) +func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef { + w := windowFor(h) state := w.w.EditorState() start, end := state.Snippet.Start, state.Snippet.End if start > end { @@ -701,8 +690,8 @@ func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NS } //export gio_insertText -func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) { - w := mustView(view) +func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) { + w := windowFor(h) state := w.w.EditorState() rng := state.compose if rng.Start == -1 { @@ -726,13 +715,13 @@ func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) { } //export gio_characterIndexForPoint -func gio_characterIndexForPoint(view C.CFTypeRef, p C.NSPoint) C.NSUInteger { +func gio_characterIndexForPoint(h C.uintptr_t, p C.NSPoint) C.NSUInteger { return C.NSNotFound } //export gio_firstRectForCharacterRange -func gio_firstRectForCharacterRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.NSRect { - w := mustView(view) +func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.NSRect { + w := windowFor(h) state := w.w.EditorState() sel := state.Selection u16start := state.UTF16Index(sel.Start) @@ -813,40 +802,40 @@ func configFor(scale float32) unit.Metric { } //export gio_onClose -func gio_onClose(view C.CFTypeRef) { - w := mustView(view) +func gio_onClose(h C.uintptr_t) { + w := windowFor(h) w.ProcessEvent(ViewEvent{}) w.setStage(StagePaused) w.ProcessEvent(DestroyEvent{}) w.displayLink.Close() w.displayLink = nil - delete(viewMap, view) + cgo.Handle(h).Delete() C.CFRelease(w.view) w.view = 0 } //export gio_onHide -func gio_onHide(view C.CFTypeRef) { - w := mustView(view) +func gio_onHide(h C.uintptr_t) { + w := windowFor(h) w.setStage(StagePaused) } //export gio_onShow -func gio_onShow(view C.CFTypeRef) { - w := mustView(view) +func gio_onShow(h C.uintptr_t) { + w := windowFor(h) w.setStage(StageRunning) } //export gio_onFullscreen -func gio_onFullscreen(view C.CFTypeRef) { - w := mustView(view) +func gio_onFullscreen(h C.uintptr_t) { + w := windowFor(h) w.config.Mode = Fullscreen w.ProcessEvent(ConfigEvent{Config: w.config}) } //export gio_onWindowed -func gio_onWindowed(view C.CFTypeRef) { - w := mustView(view) +func gio_onWindowed(h C.uintptr_t) { + w := windowFor(h) w.config.Mode = Windowed w.ProcessEvent(ConfigEvent{Config: w.config}) } @@ -910,7 +899,7 @@ func (w *window) init() error { C.CFRelease(view) return err } - insertView(view, w) + C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w))) w.view = view return nil } diff --git a/app/os_macos.m b/app/os_macos.m index 92a126bc..ae394e94 100644 --- a/app/os_macos.m +++ b/app/os_macos.m @@ -14,40 +14,50 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void); @interface GioWindowDelegate : NSObject @end +@interface GioView : NSView +@property uintptr_t handle; +@end + @implementation GioWindowDelegate - (void)windowWillMiniaturize:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; - gio_onHide((__bridge CFTypeRef)window.contentView); + GioView *view = (GioView *)window.contentView; + gio_onHide(view.handle); } - (void)windowDidDeminiaturize:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; - gio_onShow((__bridge CFTypeRef)window.contentView); + GioView *view = (GioView *)window.contentView; + gio_onShow(view.handle); } - (void)windowWillEnterFullScreen:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; - gio_onFullscreen((__bridge CFTypeRef)window.contentView); + GioView *view = (GioView *)window.contentView; + gio_onFullscreen(view.handle); } - (void)windowWillExitFullScreen:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; - gio_onWindowed((__bridge CFTypeRef)window.contentView); + GioView *view = (GioView *)window.contentView; + gio_onWindowed(view.handle); } - (void)windowDidChangeScreen:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue]; - CFTypeRef view = (__bridge CFTypeRef)window.contentView; - gio_onChangeScreen(view, dispID); + GioView *view = (GioView *)window.contentView; + gio_onChangeScreen(view.handle, dispID); } - (void)windowDidBecomeKey:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; - gio_onFocus((__bridge CFTypeRef)window.contentView, 1); + GioView *view = (GioView *)window.contentView; + gio_onFocus(view.handle, 1); } - (void)windowDidResignKey:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; - gio_onFocus((__bridge CFTypeRef)window.contentView, 0); + GioView *view = (GioView *)window.contentView; + gio_onFocus(view.handle, 0); } @end -static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) { +static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) { NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil]; if (!event.hasPreciseScrollingDeltas) { // dx and dy are in rows and columns. @@ -56,12 +66,9 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo } // Origin is in the lower left corner. Convert to upper left. CGFloat height = view.bounds.size.height; - gio_onMouse((__bridge CFTypeRef)view, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]); + gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]); } -@interface GioView : NSView -@end - @implementation GioView - (void)setFrameSize:(NSSize)newSize { [super setFrameSize:newSize]; @@ -70,11 +77,11 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo // drawRect is called when OpenGL is used, displayLayer otherwise. // Don't know why. - (void)drawRect:(NSRect)r { - gio_onDraw((__bridge CFTypeRef)self); + gio_onDraw(self.handle); } - (void)displayLayer:(CALayer *)layer { layer.contentsScale = self.window.backingScaleFactor; - gio_onDraw((__bridge CFTypeRef)self); + gio_onDraw(self.handle); } - (CALayer *)makeBackingLayer { CALayer *layer = gio_layerFactory(); @@ -83,7 +90,7 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo } - (void)viewDidMoveToWindow { if (self.window == nil) { - gio_onClose((__bridge CFTypeRef)self); + gio_onClose(self.handle); } } - (void)mouseDown:(NSEvent *)event { @@ -124,14 +131,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo - (void)keyDown:(NSEvent *)event { [self interpretKeyEvents:[NSArray arrayWithObject:event]]; NSString *keys = [event charactersIgnoringModifiers]; - gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true); + gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true); } - (void)keyUp:(NSEvent *)event { NSString *keys = [event charactersIgnoringModifiers]; - gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false); + gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false); } - (void)insertText:(id)string { - gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string); + gio_onText(self.handle, (__bridge CFTypeRef)string); } - (void)doCommandBySelector:(SEL)sel { // Don't pass commands up the responder chain. @@ -139,17 +146,17 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo } - (BOOL)hasMarkedText { - int res = gio_hasMarkedText((__bridge CFTypeRef)self); + int res = gio_hasMarkedText(self.handle); return res ? YES : NO; } - (NSRange)markedRange { - return gio_markedRange((__bridge CFTypeRef)self); + return gio_markedRange(self.handle); } - (NSRange)selectedRange { - return gio_selectedRange((__bridge CFTypeRef)self); + return gio_selectedRange(self.handle); } - (void)unmarkText { - gio_unmarkText((__bridge CFTypeRef)self); + gio_unmarkText(self.handle); } - (void)setMarkedText:(id)string selectedRange:(NSRange)selRange @@ -161,14 +168,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo } else { str = string; } - gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange); + gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange); } - (NSArray *)validAttributesForMarkedText { return nil; } - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange { - NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange)); + NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange)); return [[NSAttributedString alloc] initWithString:str attributes:nil]; } - (void)insertText:(id)string @@ -180,22 +187,22 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo } else { str = string; } - gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange); + gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange); } - (NSUInteger)characterIndexForPoint:(NSPoint)p { - return gio_characterIndexForPoint((__bridge CFTypeRef)self, p); + return gio_characterIndexForPoint(self.handle, p); } - (NSRect)firstRectForCharacterRange:(NSRange)rng actualRange:(NSRangePointer)actual { - NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual); + NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual); r = [self convertRect:r toView:nil]; return [[self window] convertRectToScreen:r]; } - (void)applicationWillUnhide:(NSNotification *)notification { - gio_onShow((__bridge CFTypeRef)self); + gio_onShow(self.handle); } - (void)applicationDidHide:(NSNotification *)notification { - gio_onHide((__bridge CFTypeRef)self); + gio_onHide(self.handle); } @end @@ -393,6 +400,11 @@ CFTypeRef gio_createView(void) { } } +void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) { + GioView *v = (__bridge GioView *)viewRef; + v.handle = handle; +} + @implementation GioAppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];