diff --git a/crates/header-translator/src/data/AppKit.rs b/crates/header-translator/src/data/AppKit.rs index df517b303..fab0b72b4 100644 --- a/crates/header-translator/src/data/AppKit.rs +++ b/crates/header-translator/src/data/AppKit.rs @@ -1,5 +1,253 @@ +// Most of these are marked as MainThreadOnly automatically data! { + // TODO: This should be one of MainThreadOnly or Immutable (+Send/Sync) + class NSAppearance { + unsafe -appearanceNamed; + unsafe -bestMatchFromAppearancesWithNames; + } + + class NSApplication { + unsafe +sharedApplication; + + unsafe -currentEvent; + unsafe -postEvent_atStart; + unsafe -presentationOptions; + unsafe -windows; + unsafe -keyWindow; + unsafe -setDelegate; + unsafe -setPresentationOptions; + unsafe -hide; + unsafe -orderFrontCharacterPalette; + unsafe -hideOtherApplications; + unsafe -stop; + unsafe -activateIgnoringOtherApps; + unsafe -requestUserAttention; + unsafe -setActivationPolicy; + unsafe -setMainMenu; + unsafe -effectiveAppearance; + unsafe -setAppearance; + + // `run` cannot be safe, the user must ensure there is no re-entrancy. + } + + // Documentation says: + // > Color objects are immutable and thread-safe + // + // TODO: Send + Sync + class NSColor: Immutable { + unsafe -clear; + } + + class NSControl { + unsafe -isEnabled; + unsafe -setEnabled; + } + + // NSCursor is immutable, stated here: + // https://developer.apple.com/documentation/appkit/nscursor/1527062-image?language=objc + // + // TODO: Send + Sync + class NSCursor: Immutable { + unsafe -initWithImage_hotSpot; + + unsafe -arrowCursor; + unsafe -IBeamCursor; + unsafe -pointingHandCursor; + unsafe -closedHandCursor; + unsafe -openHandCursor; + unsafe -resizeLeftCursor; + unsafe -resizeRightCursor; + unsafe -resizeLeftRightCursor; + unsafe -resizeUpCursor; + unsafe -resizeDownCursor; + unsafe -resizeUpDownCursor; + unsafe -crosshairCursor; + unsafe -disappearingItemCursor; + unsafe -operationNotAllowedCursor; + unsafe -dragLinkCursor; + unsafe -dragCopyCursor; + unsafe -contextualMenuCursor; + unsafe -IBeamCursorForVerticalLayout; + } + + // Since this is immutable, it _may_ be possible to make Send+Sync, but + // let's refrain from doing so, because of: + // > Safely handled only on the same thread, whether that be the main + // > thread or a secondary thread; otherwise you run the risk of having + // > events get out of sequence. + // + // + class NSEvent: Immutable { + + } + + // Documented Thread-Unsafe, but: + // > One thread can create an NSImage object, draw to the image buffer, + // > and pass it off to the main thread for drawing. The underlying image + // > cache is shared among all threads. + // + // + // So really only unsafe to mutate on several threads. + // + // Unsure yet if it would be beneficial to mark this as `Mutable`, or if + // we should just keep it as interiormutable? + class NSImage { + unsafe -initWithData; + unsafe -initByReferencingFile; + } + + class NSMenu: MainThreadOnly { + unsafe -init; + unsafe -addItem; + } + + // Any modification of the target or the action has to remain `unsafe` + class NSMenuItem: MainThreadOnly { + unsafe -init; + unsafe +separatorItem; + unsafe -setKeyEquivalentModifierMask; + unsafe -setSubmenu; + } + + class NSPasteboard { + unsafe -propertyListForType; + } + + // Documented as "Thread-Unsafe" + class NSResponder {} + + // Accesses the shared application, and hence is main thread only (even + // though not marked so in Swift). + class NSScreen: MainThreadOnly { + unsafe +mainScreen; + unsafe +screens; + unsafe -frame; + unsafe -visibleFrame; + unsafe -deviceDescription; + unsafe -backingScaleFactor; + } + + class NSWindowTabGroup: MainThreadOnly { + unsafe -windows; + unsafe -setSelectedWindow; + } + + class NSTextInputContext: MainThreadOnly { + unsafe -invalidateCharacterCoordinates; + unsafe -discardMarkedText; + unsafe -selectedKeyboardInputSource; + } + // Subclasses `NSMutableAttributedString`, though I think this should // actually be `InteriorMutable`? class NSTextStorage: Mutable {} + + // Documented as "Main Thread Only". + // > generally thread safe, although operations on views such as creating, + // > resizing, and moving should happen on the main thread. + // + // + // > If you want to use a thread to draw to a view, bracket all drawing code + // > between the lockFocusIfCanDraw and unlockFocus methods of NSView. + // + class NSView { + unsafe -frame; + unsafe -bounds; + unsafe -inputContext; + unsafe -visibleRect; + unsafe -hasMarkedText; + unsafe -convertPoint_fromView; + unsafe -window; + + unsafe -setWantsBestResolutionOpenGLSurface; + unsafe -setWantsLayer; + unsafe -setPostsFrameChangedNotifications; + unsafe -removeTrackingRect; + unsafe -addCursorRect_cursor; + unsafe -setHidden; + } + + // Documented as "Main Thread Only", but: + // > Thread safe in that you can create and manage them on a secondary + // > thread. + // + // + // + // So could in theory be `Send`, and perhaps also `Sync` - but we would + // like interior mutability on windows, since that's just much easier, and + // in that case, they can't be! + class NSWindow { + // Initializers are not safe, since it is critical to memory safety + // that `window.setReleasedWhenClosed(false)` is called. + + unsafe -frame; + unsafe -backingScaleFactor; + unsafe -contentView; + unsafe -setContentView; + unsafe -setInitialFirstResponder; + unsafe -makeFirstResponder; + unsafe -contentRectForFrameRect; + unsafe -screen; + unsafe -setContentSize; + unsafe -setFrameTopLeftPoint; + unsafe -setMinSize; + unsafe -setMaxSize; + unsafe -setResizeIncrements; + unsafe -contentResizeIncrements; + unsafe -setContentResizeIncrements; + unsafe -setFrame_display; + unsafe -setMovable; + unsafe -setSharingType; + unsafe -setTabbingMode; + unsafe -setOpaque; + unsafe -hasShadow; + unsafe -setHasShadow; + unsafe -setIgnoresMouseEvents; + unsafe -setBackgroundColor; + unsafe -styleMask; + unsafe -setStyleMask; + unsafe -registerForDraggedTypes; + unsafe -makeKeyAndOrderFront; + unsafe -orderFront; + unsafe -miniaturize; + unsafe -sender; + unsafe -toggleFullScreen; + unsafe -orderOut; + unsafe -zoom; + unsafe -selectNextKeyView; + unsafe -selectPreviousKeyView; + unsafe -firstResponder; + unsafe -standardWindowButton; + unsafe -setTitle; + unsafe -title; + unsafe -setAcceptsMouseMovedEvents; + unsafe -setTitlebarAppearsTransparent; + unsafe -setTitleVisibility; + unsafe -setMovableByWindowBackground; + unsafe -setLevel; + unsafe -setAllowsAutomaticWindowTabbing; + unsafe -setTabbingIdentifier; + unsafe -setDocumentEdited; + unsafe -occlusionState; + unsafe -center; + unsafe -isResizable; + unsafe -isMiniaturizable; + unsafe -hasCloseBox; + unsafe -isMiniaturized; + unsafe -isVisible; + unsafe -isKeyWindow; + unsafe -isZoomed; + unsafe -allowsAutomaticWindowTabbing; + unsafe -selectNextTab; + unsafe -tabbingIdentifier; + unsafe -tabGroup; + unsafe -isDocumentEdited; + unsafe -close; + unsafe -performWindowDragWithEvent; + unsafe -invalidateCursorRectsForView; + unsafe -setDelegate; + unsafe -sendEvent; + + // `addChildWindow:ordered:` is not safe, as cycles must be prevented + } } diff --git a/crates/header-translator/src/data/Foundation.rs b/crates/header-translator/src/data/Foundation.rs index d9264626e..928fc67fb 100644 --- a/crates/header-translator/src/data/Foundation.rs +++ b/crates/header-translator/src/data/Foundation.rs @@ -139,12 +139,12 @@ data! { unsafe -setName; } - class NSValue { + class NSValue: Immutable { unsafe -objCType; unsafe -isEqualToValue; } - class NSUUID { + class NSUUID: Immutable { unsafe +UUID; unsafe -init; unsafe -initWithUUIDString; @@ -186,7 +186,7 @@ data! { class NSIndexSet: ImmutableWithMutableSubclass {} class NSMutableIndexSet: MutableWithImmutableSuperclass {} - class NSNumber { + class NSNumber: Immutable { unsafe -initWithChar; unsafe -initWithUnsignedChar; unsafe -initWithShort; @@ -237,6 +237,8 @@ data! { unsafe -stringValue; } + class NSDecimalNumber: Immutable {} + class NSURLRequest: ImmutableWithMutableSubclass {} class NSMutableURLRequest: MutableWithImmutableSuperclass {} } diff --git a/crates/header-translator/src/data/macros.rs b/crates/header-translator/src/data/macros.rs index e6029fd04..b989441dc 100644 --- a/crates/header-translator/src/data/macros.rs +++ b/crates/header-translator/src/data/macros.rs @@ -69,6 +69,9 @@ macro_rules! __set_mutability { ($data:expr; Mutable) => { $data.mutability = $crate::stmt::Mutability::Mutable; }; + ($data:expr; MainThreadOnly) => { + $data.mutability = $crate::stmt::Mutability::MainThreadOnly; + }; } macro_rules! __data_inner { diff --git a/crates/header-translator/src/method.rs b/crates/header-translator/src/method.rs index 5435b7c8d..4e1987531 100644 --- a/crates/header-translator/src/method.rs +++ b/crates/header-translator/src/method.rs @@ -266,7 +266,11 @@ impl Method { } pub(crate) fn usable_in_default_id(&self) -> bool { - self.selector == "new" && self.is_class && self.arguments.is_empty() && self.safe + self.selector == "new" + && self.is_class + && self.arguments.is_empty() + && self.safe + && !self.mainthreadonly } fn parent_type_data(entity: &Entity<'_>, context: &Context<'_>) -> (bool, bool) { diff --git a/crates/header-translator/translation-config.toml b/crates/header-translator/translation-config.toml index 31027f382..f0866a914 100644 --- a/crates/header-translator/translation-config.toml +++ b/crates/header-translator/translation-config.toml @@ -28,6 +28,7 @@ tvos = "9.0" [library.AppKit] imports = ["CoreData", "Foundation"] gnustep-library = "gnustep-gui" +additions = true fixes = true extra-features = [ # Temporary, since some structs and statics use these diff --git a/crates/icrate/CHANGELOG.md b/crates/icrate/CHANGELOG.md index f091fb1d2..772ccb3b3 100644 --- a/crates/icrate/CHANGELOG.md +++ b/crates/icrate/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added `MainThreadMarker` `From` implementation for `MainThreadOnly` types. * Added `Send` and `Sync` implementations for a bunch more types (same as the ones Swift marks as `@Sendable`). +* Made some common methods in `AppKit` safe. ### Changed * Moved the `ns_string!` macro to `icrate::Foundation::ns_string`. The old @@ -39,12 +40,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). let view = unsafe { NSView::initWithFrame(mtm.alloc(), frame) }; // Do something with `app` and `view` ``` +* **BREAKING**: Changed the `NSApp` static to be a function taking `MainThreadMarker`. ### Removed * **BREAKING**: Removed the `MainThreadMarker` argument from the closure passed to `MainThreadBound::get_on_main`. -* **BREAKING**: Removed the `NSApp` static for now - it will likely be - re-added later in another form. ## icrate 0.0.4 - 2023-07-31 diff --git a/crates/icrate/examples/browser.rs b/crates/icrate/examples/browser.rs index 37c256ddf..a0383d118 100644 --- a/crates/icrate/examples/browser.rs +++ b/crates/icrate/examples/browser.rs @@ -168,7 +168,7 @@ declare_class!( // create the window content view let content_view = { - let frame_rect = unsafe { window.frame() }; + let frame_rect = window.frame(); let this = unsafe { NSStackView::initWithFrame(mtm.alloc(), frame_rect) }; unsafe { this.setOrientation(NSUserInterfaceLayoutOrientationVertical); @@ -196,14 +196,14 @@ declare_class!( // create the menu with a "quit" entry unsafe { - let menu = NSMenu::initWithTitle(NSMenu::alloc(), ns_string!("")); + let menu = NSMenu::initWithTitle(mtm.alloc(), ns_string!("")); let menu_app_item = NSMenuItem::initWithTitle_action_keyEquivalent( - NSMenuItem::alloc(), + mtm.alloc(), ns_string!(""), None, ns_string!(""), ); - let menu_app_menu = NSMenu::initWithTitle(NSMenu::alloc(), ns_string!("")); + let menu_app_menu = NSMenu::initWithTitle(mtm.alloc(), ns_string!("")); menu_app_menu.addItemWithTitle_action_keyEquivalent( ns_string!("Quit"), Some(sel!(terminate:)), @@ -217,12 +217,10 @@ declare_class!( } // configure the window - unsafe { - window.setContentView(Some(&content_view)); - window.center(); - window.setTitle(ns_string!("browser example")); - window.makeKeyAndOrderFront(None); - } + window.setContentView(Some(&content_view)); + window.center(); + window.setTitle(ns_string!("browser example")); + window.makeKeyAndOrderFront(None); // request the web view navigate to a page unsafe { @@ -290,17 +288,15 @@ unsafe impl NSObjectProtocol for Delegate {} fn main() { let mtm = MainThreadMarker::new().unwrap(); - let app = unsafe { NSApplication::sharedApplication(mtm) }; - unsafe { app.setActivationPolicy(NSApplicationActivationPolicyRegular) }; + let app = NSApplication::sharedApplication(mtm); + app.setActivationPolicy(NSApplicationActivationPolicyRegular); // initialize the delegate let delegate = Delegate::new(mtm); // configure the application delegate - unsafe { - let object = ProtocolObject::from_ref(&*delegate); - app.setDelegate(Some(object)) - }; + let object = ProtocolObject::from_ref(&*delegate); + app.setDelegate(Some(object)); // run the app unsafe { app.run() }; diff --git a/crates/icrate/examples/delegate.rs b/crates/icrate/examples/delegate.rs index bb954fb0d..248b7e487 100644 --- a/crates/icrate/examples/delegate.rs +++ b/crates/icrate/examples/delegate.rs @@ -76,8 +76,8 @@ impl AppDelegate { fn main() { let mtm: MainThreadMarker = MainThreadMarker::new().unwrap(); - let app = unsafe { NSApplication::sharedApplication(mtm) }; - unsafe { app.setActivationPolicy(NSApplicationActivationPolicyRegular) }; + let app = NSApplication::sharedApplication(mtm); + app.setActivationPolicy(NSApplicationActivationPolicyRegular); // initialize the delegate let delegate = AppDelegate::new(42, true, mtm); @@ -85,10 +85,8 @@ fn main() { println!("{delegate:?}"); // configure the application delegate - unsafe { - let object = ProtocolObject::from_ref(&*delegate); - app.setDelegate(Some(object)); - }; + let object = ProtocolObject::from_ref(&*delegate); + app.setDelegate(Some(object)); // run the app unsafe { app.run() }; diff --git a/crates/icrate/examples/metal.rs b/crates/icrate/examples/metal.rs index 8c64ae2cd..47652f699 100644 --- a/crates/icrate/examples/metal.rs +++ b/crates/icrate/examples/metal.rs @@ -184,7 +184,7 @@ declare_class!( // create the metal view let mtk_view = { - let frame_rect = unsafe { window.frame() }; + let frame_rect = window.frame(); unsafe { MTKView::initWithFrame_device(mtm.alloc(), frame_rect, Some(&device)) } }; @@ -223,12 +223,10 @@ declare_class!( } // configure the window - unsafe { - window.setContentView(Some(&mtk_view)); - window.center(); - window.setTitle(ns_string!("metal example")); - window.makeKeyAndOrderFront(None); - } + window.setContentView(Some(&mtk_view)); + window.center(); + window.setTitle(ns_string!("metal example")); + window.makeKeyAndOrderFront(None); // initialize the delegate state self.command_queue.replace(Some(command_queue)); @@ -360,17 +358,15 @@ impl Delegate { fn main() { let mtm = MainThreadMarker::new().unwrap(); // configure the app - let app = unsafe { NSApplication::sharedApplication(mtm) }; - unsafe { app.setActivationPolicy(NSApplicationActivationPolicyRegular) }; + let app = NSApplication::sharedApplication(mtm); + app.setActivationPolicy(NSApplicationActivationPolicyRegular); // initialize the delegate let delegate = Delegate::new(mtm); // configure the application delegate - unsafe { - let object = ProtocolObject::from_ref(&*delegate); - app.setDelegate(Some(object)) - }; + let object = ProtocolObject::from_ref(&*delegate); + app.setDelegate(Some(object)); // run the app unsafe { app.run() }; diff --git a/crates/icrate/src/additions/AppKit/mod.rs b/crates/icrate/src/additions/AppKit/mod.rs new file mode 100644 index 000000000..f37c7ec67 --- /dev/null +++ b/crates/icrate/src/additions/AppKit/mod.rs @@ -0,0 +1,7 @@ +use crate::common::*; + +#[cfg(feature = "AppKit_NSApplication")] +pub fn NSApp(mtm: crate::Foundation::MainThreadMarker) -> Id { + // TODO: Use the `NSApp` static + crate::AppKit::NSApplication::sharedApplication(mtm) +} diff --git a/crates/icrate/src/generated b/crates/icrate/src/generated index 24a19eb14..560cb6a40 160000 --- a/crates/icrate/src/generated +++ b/crates/icrate/src/generated @@ -1 +1 @@ -Subproject commit 24a19eb1494a964312ba5d26ebbffb05436973a5 +Subproject commit 560cb6a40f078eed5a6e1af49fdd5299b1ad0ba1