From a5eaac55cae505c88759bb695e4856c32b77b7e0 Mon Sep 17 00:00:00 2001 From: Grant Gurvis Date: Mon, 30 Dec 2024 20:42:01 -0500 Subject: [PATCH 1/4] fix: migrate some of the codebase to objc2 --- Cargo.lock | 56 +-- crates/fig_desktop/Cargo.toml | 7 +- crates/fig_desktop/src/main.rs | 4 - crates/fig_desktop/src/platform/macos.rs | 23 +- crates/fig_desktop_api/src/init_script.rs | 2 +- crates/fig_input_method/Cargo.toml | 14 +- crates/fig_input_method/Makefile | 22 -- crates/fig_input_method/src/imk.rs | 318 +++++++++--------- crates/fig_input_method/src/macos.rs | 59 ++++ crates/fig_input_method/src/main.rs | 72 +--- crates/fig_util/Cargo.toml | 4 +- crates/fig_util/src/open.rs | 25 +- crates/macos-utils/Cargo.toml | 22 ++ crates/macos-utils/src/accessibility.rs | 22 +- crates/macos-utils/src/applications.rs | 73 ++-- crates/macos-utils/src/bundle.rs | 20 +- crates/macos-utils/src/image.rs | 122 +++---- crates/macos-utils/src/lib.rs | 10 - crates/macos-utils/src/os.rs | 45 +-- crates/macos-utils/src/url.rs | 29 +- crates/macos-utils/src/util/mod.rs | 24 +- .../src/util/notification_center.rs | 165 +++++---- crates/macos-utils/src/util/nsstring.rs | 59 +--- crates/macos-utils/src/util/nsurl.rs | 30 -- crates/macos-utils/src/window_server/mod.rs | 278 +++++++-------- crates/q_cli/Cargo.toml | 3 + crates/q_cli/src/util/desktop.rs | 130 ++++--- package.json | 2 +- 28 files changed, 706 insertions(+), 934 deletions(-) delete mode 100644 crates/fig_input_method/Makefile create mode 100644 crates/fig_input_method/src/macos.rs delete mode 100644 crates/macos-utils/src/util/nsurl.rs diff --git a/Cargo.lock b/Cargo.lock index a67ce310e..465a4757d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2814,8 +2814,9 @@ dependencies = [ "nix 0.29.0", "notify", "objc", - "objc-foundation", - "objc_id", + "objc2", + "objc2-app-kit", + "objc2-foundation", "once_cell", "parking_lot", "paste", @@ -2921,16 +2922,16 @@ name = "fig_input_method" version = "1.5.1" dependencies = [ "apple-bundle", - "cocoa", "fig_ipc", "fig_log", "fig_proto", "fig_util", - "libc", "macos-utils", - "objc", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-input-method-kit", "plist", - "rand 0.8.5", "serde", "tokio", "toml", @@ -3224,7 +3225,9 @@ dependencies = [ "libc", "macos-utils", "nix 0.29.0", - "objc", + "objc2", + "objc2-app-kit", + "objc2-foundation", "paste", "rand 0.8.5", "regex", @@ -5002,14 +5005,19 @@ dependencies = [ "accessibility-sys", "appkit-nsworkspace-bindings", "block", + "block2", "cocoa", "core-foundation 0.10.0", "core-graphics", "dashmap", "flume", "fnv", + "libc", "nix 0.29.0", "objc", + "objc2", + "objc2-app-kit", + "objc2-foundation", "once_cell", "tokio", "tracing", @@ -5721,17 +5729,6 @@ dependencies = [ "malloc_buf", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -5846,6 +5843,17 @@ dependencies = [ "objc2", ] +[[package]] +name = "objc2-input-method-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e306c83555cdab87755f31b45f8d9a657081edb15fc89c74a5bb4175ebd487" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -5951,15 +5959,6 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "object" version = "0.32.2" @@ -6761,6 +6760,9 @@ dependencies = [ "mimalloc", "nix 0.29.0", "objc", + "objc2", + "objc2-app-kit", + "objc2-foundation", "once_cell", "owo-colors 4.1.0", "parking_lot", diff --git a/crates/fig_desktop/Cargo.toml b/crates/fig_desktop/Cargo.toml index 9cf2141fc..83847e03f 100644 --- a/crates/fig_desktop/Cargo.toml +++ b/crates/fig_desktop/Cargo.toml @@ -135,10 +135,11 @@ core-foundation.workspace = true core-graphics.workspace = true libc.workspace = true macos-utils = { path = "../macos-utils" } -objc.workspace = true +objc = "0.2.7" +objc2 = "0.5.2" +objc2-app-kit = { version = "0.2.2", features = ["NSApplication"] } +objc2-foundation = "0.2.2" system-configuration = "0.6.0" -objc-foundation = "0.1.1" -objc_id = "0.1.1" [build-dependencies] image = "0.25.1" diff --git a/crates/fig_desktop/src/main.rs b/crates/fig_desktop/src/main.rs index c4a1f05e1..f05be6f4c 100644 --- a/crates/fig_desktop/src/main.rs +++ b/crates/fig_desktop/src/main.rs @@ -71,10 +71,6 @@ use webview::{ // #[global_allocator] // static GLOBAL: Jemalloc = Jemalloc; -#[cfg(target_os = "macos")] -#[macro_use] -extern crate objc; - #[derive(Debug, Default)] pub struct InterceptState { pub intercept_bound_keystrokes: RwLock, diff --git a/crates/fig_desktop/src/platform/macos.rs b/crates/fig_desktop/src/platform/macos.rs index ddbcf12e8..ff9d4cc11 100644 --- a/crates/fig_desktop/src/platform/macos.rs +++ b/crates/fig_desktop/src/platform/macos.rs @@ -43,7 +43,6 @@ use macos_utils::window_server::{ UIElement, }; use macos_utils::{ - NSString, NotificationCenter, WindowServer, WindowServerEvent, @@ -65,6 +64,11 @@ use objc::{ sel, sel_impl, }; +use objc2_foundation::{ + NSDictionary, + NSOperationQueue, + ns_string, +}; use once_cell::sync::Lazy; use parking_lot::{ Mutex, @@ -132,8 +136,8 @@ static UNMANAGED: Lazy = Lazy::new(|| Unmanaged { static ACCESSIBILITY_ENABLED: Lazy = Lazy::new(|| AtomicBool::new(accessibility_is_enabled())); static MACOS_VERSION: Lazy = Lazy::new(|| { - let version = macos_utils::os::NSOperatingSystemVersion::get(); - semver::Version::new(version.major as u64, version.minor as u64, version.patch as u64) + let version = macos_utils::os::OperatingSystemVersion::get(); + semver::Version::new(version.major() as u64, version.minor() as u64, version.patch() as u64) }); pub static ACTIVATION_POLICY: Mutex = Mutex::new(ActivationPolicy::Regular); @@ -323,12 +327,9 @@ impl PlatformStateImpl { let accessibility_proxy = self.proxy.clone(); let mut distributed = NotificationCenter::distributed_center(); - let ax_notification_name: NSString = "com.apple.accessibility.api".into(); - let queue: id = unsafe { - let queue: id = msg_send![class!(NSOperationQueue), alloc]; - msg_send![queue, init] - }; - distributed.subscribe(ax_notification_name, Some(queue), move |_| { + let ax_notification_name = ns_string!("com.apple.accessibility.api"); + let queue = unsafe { NSOperationQueue::new() }; + distributed.subscribe(ax_notification_name, Some(&queue), move |_| { accessibility_proxy .clone() .send_event(Event::PlatformBoundEvent( @@ -687,8 +688,8 @@ impl PlatformStateImpl { if !is_xterm && supports_ime { tracing::debug!("Sending notif com.amazon.codewhisperer.edit_buffer_updated"); NotificationCenter::distributed_center().post_notification( - "com.amazon.codewhisperer.edit_buffer_updated", - std::iter::empty::<(&str, &str)>(), + ns_string!("com.amazon.codewhisperer.edit_buffer_updated"), + &NSDictionary::new(), ); } else { let caret = if is_xterm { diff --git a/crates/fig_desktop_api/src/init_script.rs b/crates/fig_desktop_api/src/init_script.rs index 1dc188572..677bfd2cd 100644 --- a/crates/fig_desktop_api/src/init_script.rs +++ b/crates/fig_desktop_api/src/init_script.rs @@ -115,7 +115,7 @@ impl Constants { api_proto_url: "api://localhost".to_string(), midway: midway_cookie_path().map_or(false, |p| p.is_file()), #[cfg(target_os = "macos")] - macos_version: macos_utils::os::NSOperatingSystemVersion::get().to_string(), + macos_version: macos_utils::os::OperatingSystemVersion::get().to_string(), #[cfg(target_os = "linux")] linux: LinuxConstants { display_server: get_display_server(&fig_os_shim::Context::new()).ok(), diff --git a/crates/fig_input_method/Cargo.toml b/crates/fig_input_method/Cargo.toml index 210f4e0f5..4ffbbed69 100644 --- a/crates/fig_input_method/Cargo.toml +++ b/crates/fig_input_method/Cargo.toml @@ -24,14 +24,20 @@ toml.workspace = true apple-bundle = "0.1.4" [target.'cfg(target_os = "macos")'.dependencies] -objc.workspace = true -cocoa.workspace = true -libc.workspace = true -rand.workspace = true fig_ipc.workspace = true fig_log.workspace = true fig_proto.workspace = true fig_util.workspace = true macos-utils = { path = "../macos-utils" } +objc2 = "0.5.2" +objc2-app-kit = { version = "0.2.2", features = [ + "NSApplication", + "NSResponder", +] } +objc2-foundation = { version = "0.2.2", features = ["NSThread"] } +objc2-input-method-kit = { version = "0.2.2", features = [ + "IMKServer", + "IMKInputController", +] } tokio.workspace = true tracing.workspace = true diff --git a/crates/fig_input_method/Makefile b/crates/fig_input_method/Makefile deleted file mode 100644 index d854c8173..000000000 --- a/crates/fig_input_method/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -output_dir = build -app_name = CodeWhispererInputMethod -bin_name = fig_input_method - -.PHONY: bundle -bundle: - cargo build --release - mkdir -p $(output_dir)/$(app_name).app/Contents - mkdir -p $(output_dir)/$(app_name).app/Contents/MacOS - mkdir -p $(output_dir)/$(app_name).app/Contents/Resources - cp Info.plist $(output_dir)/$(app_name).app/Contents/ - pwd - ditto ../target/release/$(bin_name) $(output_dir)/$(app_name).app/Contents/MacOS/ - cp -r resources/* $(output_dir)/$(app_name).app/Contents/Resources/ - -.PHONY: clean -clean: - rm -rf $(output_dir)/$(app_name).app - -.PHONY: run -run: bundle - ./$(output_dir)/$(app_name).app/Contents/MacOS/$(bin_name) diff --git a/crates/fig_input_method/src/imk.rs b/crates/fig_input_method/src/imk.rs index cc5ebd596..105e6e656 100644 --- a/crates/fig_input_method/src/imk.rs +++ b/crates/fig_input_method/src/imk.rs @@ -1,32 +1,39 @@ -use std::ffi::CStr; +use std::cell::Cell; -use cocoa::base::{ - BOOL, - NO, - YES, - id, +use fig_ipc::local::send_hook_to_socket; +use fig_proto::hooks::new_caret_position_hook; +use fig_proto::local::caret_position_hook::Origin; +use fig_util::Terminal; +use macos_utils::NotificationCenter; +use objc2::mutability::InteriorMutable; +use objc2::rc::{ + Allocated, + Retained, +}; +use objc2::runtime::{ + AnyObject, + Bool, + Sel, +}; +use objc2::{ + ClassType, + DeclaredClass, + declare_class, + msg_send, + msg_send_id, + sel, }; -use cocoa::foundation::{ +use objc2_foundation::{ NSPoint, NSRange, NSRect, NSSize, -}; -use fig_ipc::local::send_hook_to_socket; -use fig_proto::hooks::new_caret_position_hook; -use fig_proto::local::caret_position_hook::Origin; -use fig_util::Terminal; -use macos_utils::{ NSString, - NSStringRef, - NotificationCenter, + ns_string, }; -use objc::declare::ClassDecl; -use objc::runtime::{ - Class, - Object, - Sel, - sel_getName, +use objc2_input_method_kit::{ + IMKInputController, + IMKServer, }; use tracing::{ debug, @@ -35,172 +42,153 @@ use tracing::{ warn, }; -#[link(name = "InputMethodKit", kind = "framework")] -extern "C" {} +const INPUT_CONTROLLER_CLASS_NAME: &str = env!("InputMethodServerControllerClass"); -// TODO: create trait IMKServer -pub unsafe fn connect_imkserver(name: id /* NSString */, identifier: id /* NSString */) { - info!("connecting to imkserver"); - let server_alloc: id = msg_send![class!(IMKServer), alloc]; - let _server: id = msg_send![server_alloc, initWithName:name bundleIdentifier:identifier]; - info!("connected to imkserver"); +fn bundle_identifier(client: &AnyObject) -> Option { + let bundle_id: &NSString = unsafe { msg_send![client, bundleIdentifier] }; + Some(bundle_id.to_string()) } -pub fn register_controller() { - let input_controller_class: &str = match option_env!("InputMethodServerControllerClass") { - Some(input_controller_class) => input_controller_class, - None => unreachable!("Must specify `InputMethodServerControllerClass` environment variable"), - }; - info!("registering {input_controller_class}..."); - - let super_class = class!(IMKInputController); - let mut decl = ClassDecl::new(input_controller_class, super_class).unwrap(); - - unsafe { - decl.add_ivar::("is_active"); - - decl.add_method( - sel!(initWithServer:delegate:client:), - init_with_server_delegate_client as extern "C" fn(&Object, Sel, id, id, id) -> id, - ); - - decl.add_method( - sel!(activateServer:), - activate_server as extern "C" fn(&mut Object, Sel, id), - ); - - decl.add_method( - sel!(deactivateServer:), - deactivate_server as extern "C" fn(&mut Object, Sel, id), - ); - - decl.add_method( - sel!(handleCursorPositionRequest:), - handle_cursor_position_request as extern "C" fn(&Object, Sel, id), - ); - - decl.add_method( - sel!(respondsToSelector:), - responds_to_selector as extern "C" fn(&Object, Sel, Sel) -> BOOL, - ); - } - decl.register(); - info!("finished registering {input_controller_class}."); +struct Ivars { + is_active: Cell, } -extern "C" fn init_with_server_delegate_client(this: &Object, _cmd: Sel, server: id, delegate: id, client: id) -> id { - unsafe { - info!("INITING"); - // Superclass - let super_cls = Class::get("IMKInputController").unwrap(); - let this: id = msg_send![super(this, super_cls), initWithServer:server delegate: delegate -client: client]; - - (*this).set_ivar::("is_active", NO); +declare_class!( + struct MyInputController; - let mut center = NotificationCenter::distributed_center(); - center.subscribe_with_observer( - "com.amazon.codewhisperer.edit_buffer_updated", - this, - sel!(handleCursorPositionRequest:), - ); + // - The superclass NSObject does not have any subclassing requirements. + // - Interior mutability is a safe default. + // - `MyCustomObject` does not implement `Drop`. + unsafe impl ClassType for MyInputController { + type Super = IMKInputController; + type Mutability = InteriorMutable; + const NAME: &'static str = INPUT_CONTROLLER_CLASS_NAME; + } - this + impl DeclaredClass for MyInputController { + type Ivars = Ivars; } -} -fn bundle_identifier(client: id) -> Option { - let bundle_id: NSStringRef = unsafe { msg_send![client, bundleIdentifier] }; - bundle_id.as_str().map(|s| s.into()) -} + unsafe impl MyInputController { + #[method_id(initWithServer:delegate:client:)] + fn init_with_server_delegate_client(this: Allocated, server: Option<&IMKServer>, delegate: Option<&AnyObject>, client: Option<&AnyObject>) -> Retained { + info!("INITING"); + let partial = this.set_ivars(Ivars { is_active: Cell::new(true) }); + let this: Retained = unsafe { msg_send_id![super(partial, IMKInputController::class()), initWithServer:server delegate: delegate client: client] }; + + let mut center = NotificationCenter::distributed_center(); + unsafe { + center.subscribe_with_observer( + ns_string!("com.amazon.codewhisperer.edit_buffer_updated"), + &this, + sel!(handleCursorPositionRequest:), + ); + } -extern "C" fn activate_server(this: &mut Object, _cmd: Sel, client: id) { - unsafe { - (*this).set_ivar::("is_active", YES); - let bundle_id = bundle_identifier(client); - info!("activated server: {:?}", bundle_id); + this + } - let terminal = Terminal::from_bundle_id(bundle_id.unwrap_or_default().as_str()); + #[method(activateServer:)] + fn activate_server(&self, client: Option<&AnyObject>) { + let client = client.unwrap(); + self.ivars().is_active.set(true); - // Used to trigger input method enabled in Alacritty - if matches!(terminal, Some(Terminal::Alacritty)) { - let empty_range = NSRange::new(0, 0); - let space_string: NSString = " ".into(); - let empty_string: NSString = "".into(); + let bundle_id = bundle_identifier(client); + info!("activated server: {:?}", bundle_id); - // First, setMarkedText with a non-empty string in order to enable winit IME - // https://github.com/rust-windowing/winit/blob/97d4c7b303bb8110df6c492f0c2327b7d5098347/src/platform_impl/macos/view.rs#L330-L337 + let terminal = Terminal::from_bundle_id(bundle_id.unwrap_or_default().as_str()); - let _: () = msg_send![client, setMarkedText: space_string selectionRange: empty_range replacementRange: empty_range]; + // Used to trigger input method enabled in Alacritty + if matches!(terminal, Some(Terminal::Alacritty)) { + let empty_range = NSRange::new(0, 0); + let space_string = ns_string!(" "); + let empty_string = ns_string!(""); - // Then, since we don't *actually* want to be in the preedit state, set marked text to an empty - // string to invalidate https://github.com/rust-windowing/winit/blob/97d4c7b303bb8110df6c492f0c2327b7d5098347/src/platform_impl/macos/view.rs#L345-L351 - let _: () = msg_send![client, setMarkedText: empty_string selectionRange: empty_range replacementRange: empty_range]; + unsafe { + // First, setMarkedText with a non-empty string in order to enable winit IME + // https://github.com/rust-windowing/winit/blob/97d4c7b303bb8110df6c492f0c2327b7d5098347/src/platform_impl/macos/view.rs#L330-L337 + let _: () = msg_send![client, setMarkedText: space_string selectionRange: empty_range replacementRange: empty_range]; + + // Then, since we don't *actually* want to be in the preedit state, set marked text to an empty + // string to invalidate https://github.com/rust-windowing/winit/blob/97d4c7b303bb8110df6c492f0c2327b7d5098347/src/platform_impl/macos/view.rs#L345-L351 + let _: () = msg_send![client, setMarkedText: empty_string selectionRange: empty_range replacementRange: empty_range]; + } + } } - } -} -extern "C" fn deactivate_server(this: &mut Object, _cmd: Sel, client: id) { - unsafe { - (*this).set_ivar::("is_active", NO); - info!("deactivated server: {:?}", bundle_identifier(client)); - } -} + #[method(deactivateServer:)] + fn deactivate_server(&self, client: Option<&AnyObject>) { + self.ivars().is_active.set(false); + info!("deactivated server: {:?}", bundle_identifier(client.unwrap())); + } -extern "C" fn handle_cursor_position_request(this: &Object, _sel: Sel, _notif: id) { - let client: id = unsafe { msg_send![this, client] }; - let bundle_id = bundle_identifier(client); - let is_active = unsafe { this.get_ivar::("is_active") }; - - if *is_active == YES { - let terminal = Terminal::from_bundle_id(bundle_id.as_deref().unwrap_or_default()); - match terminal { - Some(term) if term.supports_macos_input_method() => { - info!("Instance {bundle_id:?} is active, handling request"); - let mut rect: NSRect = NSRect { - origin: NSPoint { x: 0.0, y: 0.0 }, - size: NSSize { - height: 0.0, - width: 0.0, + #[method(handleCursorPositionRequest:)] + fn handle_cursor_position_request(&self, _notif: Option<&AnyObject>) { + let client: &AnyObject = unsafe { msg_send![self, client] }; + let bundle_id = bundle_identifier(client); + + let is_active = self.ivars().is_active.get(); + if is_active { + let terminal = Terminal::from_bundle_id(bundle_id.as_deref().unwrap_or_default()); + match terminal { + Some(term) if term.supports_macos_input_method() => { + info!("Instance {bundle_id:?} is active, handling request"); + let mut rect: NSRect = NSRect { + origin: NSPoint { x: 0.0, y: 0.0 }, + size: NSSize { + height: 0.0, + width: 0.0, + }, + }; + let _: () = unsafe { msg_send![client, attributesForCharacterIndex: 0 lineHeightRectangle: &mut rect] }; + + let hook = new_caret_position_hook( + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + Origin::BottomLeft, + ); + + info!("Sending cursor position for {bundle_id:?}: {hook:?}"); + tokio::spawn(async { + match send_hook_to_socket(hook).await { + Ok(_) => debug!("Sent hook successfully"), + Err(_) => warn!("Failed to send hook"), + } + }); }, - }; - let _: () = unsafe { msg_send![client, attributesForCharacterIndex: 0 lineHeightRectangle: &mut rect] }; - - let hook = new_caret_position_hook( - rect.origin.x, - rect.origin.y, - rect.size.width, - rect.size.height, - Origin::BottomLeft, - ); + _ => { + info!("Instance {bundle_id:?} is not a supported terminal, ignoring request"); + }, + } + } + } - info!("Sending cursor position for {bundle_id:?}: {hook:?}"); - tokio::spawn(async { - match send_hook_to_socket(hook).await { - Ok(_) => debug!("Sent hook successfully"), - Err(_) => warn!("Failed to send hook"), - } - }); - }, - _ => { - info!("Instance {bundle_id:?} is not a supported terminal, ignoring request"); - }, + #[method(respondsToSelector:)] + fn responds_to_selector(&self, selector: Sel) -> Bool { + info!("responds_to_selector"); + info!("superclass"); + let superclass = unsafe { msg_send![self, superclass] }; + info!("should_respond"); + let should_respond: Bool = unsafe { msg_send![super(self, superclass), respondsToSelector: selector] }; + trace!("`{}` should respond? {}", selector.name(), should_respond.as_bool()); + should_respond } } +); + +pub fn connect_imkserver(name: &NSString, identifier: Option<&NSString>) { + info!("connecting to imkserver"); + let server_alloc = IMKServer::alloc(); + unsafe { IMKServer::initWithName_bundleIdentifier(server_alloc, Some(name), identifier) }; + info!("connected to imkserver"); } -extern "C" fn responds_to_selector(this: &Object, _cmd: Sel, selector: Sel) -> BOOL { - info!("responds_to_selector"); - unsafe { - info!("superclass"); - let superclass = msg_send![this, superclass]; - info!("should_respond"); - let should_respond: BOOL = msg_send![super(this, superclass), respondsToSelector: selector]; - info!("selector_name"); - let selector_name = CStr::from_ptr(sel_getName(selector)) - .to_str() - .unwrap_or("UNKNOWN SELECTOR"); - trace!("`{}` should respond? {}", selector_name, should_respond); - should_respond - } +pub fn register_controller() { + info!("registering {INPUT_CONTROLLER_CLASS_NAME}..."); + + MyInputController::class(); + + info!("finished registering {INPUT_CONTROLLER_CLASS_NAME}."); } diff --git a/crates/fig_input_method/src/macos.rs b/crates/fig_input_method/src/macos.rs new file mode 100644 index 000000000..90103a7ec --- /dev/null +++ b/crates/fig_input_method/src/macos.rs @@ -0,0 +1,59 @@ +use fig_log::{ + LogArgs, + initialize_logging, +}; +use fig_util::directories; +use objc2::rc::autoreleasepool; +use objc2::runtime::Bool; +use objc2::{ + ClassType, + msg_send, +}; +use objc2_app_kit::NSApp; +use objc2_foundation::{ + MainThreadMarker, + NSBundle, + NSObject, + ns_string, +}; +use tracing::info; + +use crate::imk; + +const CONNECTION_NAME: &str = env!("InputMethodConnectionName"); + +#[tokio::main] +pub async fn main() { + let _log_guard = initialize_logging(LogArgs { + log_level: Some("trace".to_owned()), + log_to_stdout: true, + log_file_path: Some(directories::logs_dir().expect("home dir must be set").join("imk.log")), + delete_old_log_file: false, + }); + + info!("Registering imk controller"); + imk::register_controller(); + info!("Registered imk controller"); + + let mtm = MainThreadMarker::new().expect("must be on the main thread"); + + autoreleasepool(|_pool| { + let app = NSApp(mtm); + + let k_connection_name = ns_string!(CONNECTION_NAME); + let nib_name = ns_string!("MainMenu"); + + let bundle = NSBundle::mainBundle(); + let identifier = unsafe { bundle.bundleIdentifier() }; + + info!("Attempting connection..."); + imk::connect_imkserver(k_connection_name, identifier.as_deref()); + info!("Connected!"); + + let app_id: &NSObject = app.as_ref(); + let loaded_nib: Bool = unsafe { msg_send![NSBundle::class(), loadNibNamed:nib_name owner:app_id] }; + info!("RUNNING {loaded_nib:?}!"); + + unsafe { app.run() }; + }); +} diff --git a/crates/fig_input_method/src/main.rs b/crates/fig_input_method/src/main.rs index edae21064..bfa9c5b96 100644 --- a/crates/fig_input_method/src/main.rs +++ b/crates/fig_input_method/src/main.rs @@ -1,75 +1,13 @@ -// This is needed for objc -#![allow(unexpected_cfgs)] - -#[cfg(target_os = "macos")] -#[macro_use] -extern crate objc; - #[cfg(target_os = "macos")] mod imk; - #[cfg(target_os = "macos")] -#[tokio::main] -async fn main() { - use cocoa::appkit::{ - NSApp, - NSApplication, - }; - use cocoa::base::{ - BOOL, - id, - nil, - }; - use cocoa::foundation::{ - NSAutoreleasePool, - NSString, - }; - use fig_log::{ - LogArgs, - initialize_logging, - }; - use fig_util::directories; - use tracing::info; - - let _log_guard = initialize_logging(LogArgs { - log_level: Some("trace".to_owned()), - log_to_stdout: true, - log_file_path: Some(directories::logs_dir().expect("home dir must be set").join("imk.log")), - delete_old_log_file: false, - }); - - info!("HI THERE"); - - imk::register_controller(); +mod macos; - info!("registered imk controller"); - - let connection_name: &str = match option_env!("InputMethodConnectionName") { - Some(name) => name, - None => unreachable!("InputMethodConnectionName environment var must be specified"), - }; - - unsafe { - let _pool = NSAutoreleasePool::new(nil); - let app = NSApp(); - let k_connection_name = NSString::alloc(nil).init_str(connection_name); - let nib_name = NSString::alloc(nil).init_str("MainMenu"); - - let bundle: id = msg_send![class!(NSBundle), mainBundle]; - let identifier: id = msg_send![bundle, bundleIdentifier]; - - info!("Attempting connection..."); - imk::connect_imkserver(k_connection_name, identifier); - info!("Connected!"); - - let loaded_nib: BOOL = msg_send![class!(NSBundle), loadNibNamed:nib_name - owner:app]; - info!("RUNNING {loaded_nib:?}!"); - app.run(); - } -} +#[cfg(target_os = "macos")] +pub use macos::main; #[cfg(not(target_os = "macos"))] -fn main() { +fn main() -> std::process::ExitCode { println!("Fig input method is only supported on macOS"); + std::process::ExitCode } diff --git a/crates/fig_util/Cargo.toml b/crates/fig_util/Cargo.toml index d7c88b95a..8dd0822ad 100644 --- a/crates/fig_util/Cargo.toml +++ b/crates/fig_util/Cargo.toml @@ -23,6 +23,7 @@ fig_os_shim.workspace = true hex.workspace = true indoc.workspace = true libc.workspace = true +objc2-foundation = { version = "0.2.2", features = ["NSURL"] } paste = "1.0.11" rand.workspace = true regex.workspace = true @@ -40,7 +41,8 @@ whoami.workspace = true appkit-nsworkspace-bindings.workspace = true core-foundation = "0.10.0" macos-utils = { path = "../macos-utils" } -objc.workspace = true +objc2 = "0.5.2" +objc2-app-kit = { version = "0.2.2", features = ["NSWorkspace"] } [target.'cfg(target_os = "linux")'.dependencies] bstr.workspace = true diff --git a/crates/fig_util/src/open.rs b/crates/fig_util/src/open.rs index 6f2d11579..6315adc11 100644 --- a/crates/fig_util/src/open.rs +++ b/crates/fig_util/src/open.rs @@ -11,25 +11,16 @@ pub enum Error { #[cfg(target_os = "macos")] #[allow(unexpected_cfgs)] fn open_macos(url_str: impl AsRef) -> Result<(), Error> { - use macos_utils::NSURL; - use objc::runtime::{ - BOOL, - NO, - Object, - }; - use objc::{ - class, - msg_send, - sel, - sel_impl, + use objc2::ClassType; + use objc2_foundation::{ + NSString, + NSURL, }; - let url = NSURL::from(url_str.as_ref()); - let res: BOOL = unsafe { - let shared: *mut Object = msg_send![class!(NSWorkspace), sharedWorkspace]; - msg_send![shared, openURL: url] - }; - if res != NO { Ok(()) } else { Err(Error::Failed) } + let url_nsstring = NSString::from_str(url_str.as_ref()); + let nsurl = unsafe { NSURL::initWithString(NSURL::alloc(), &url_nsstring) }.ok_or(Error::Failed)?; + let res = unsafe { objc2_app_kit::NSWorkspace::sharedWorkspace().openURL(&nsurl) }; + res.then_some(()).ok_or(Error::Failed) } #[cfg(target_os = "windows")] diff --git a/crates/macos-utils/Cargo.toml b/crates/macos-utils/Cargo.toml index 2d3a89b82..1e5e3ac14 100644 --- a/crates/macos-utils/Cargo.toml +++ b/crates/macos-utils/Cargo.toml @@ -12,7 +12,26 @@ block = "0.1.6" core-graphics.workspace = true core-foundation.workspace = true cocoa.workspace = true +libc.workspace = true objc.workspace = true +objc2 = "0.5.2" +objc2-app-kit = { version = "0.2.2", features = [ + "NSBitmapImageRep", + "NSGraphicsContext", + "NSImage", + "NSImageRep", + "NSWorkspace", + "NSRunningApplication", + "libc", +] } +objc2-foundation = { version = "0.2.2", features = [ + "block2", + "NSDictionary", + "NSDistributedNotificationCenter", + "NSEnumerator", + "NSOperation", + "NSProcessInfo", +] } dashmap.workspace = true flume = "0.11.0" fnv = "1.0.7" @@ -23,3 +42,6 @@ appkit-nsworkspace-bindings.workspace = true tracing.workspace = true tokio.workspace = true nix.workspace = true + +[dependencies] +block2 = "0.5.1" diff --git a/crates/macos-utils/src/accessibility.rs b/crates/macos-utils/src/accessibility.rs index cb00f199d..d59b069c9 100644 --- a/crates/macos-utils/src/accessibility.rs +++ b/crates/macos-utils/src/accessibility.rs @@ -1,16 +1,22 @@ use accessibility_sys::AXIsProcessTrusted; - -use crate::NSURL; -use crate::util::IdRef; +use objc2::ClassType; +use objc2_app_kit::NSWorkspace; +use objc2_foundation::{ + NSURL, + ns_string, +}; static ACCESSIBILITY_SETTINGS_URL: &str = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"; -pub fn open_accessibility() { - unsafe { - let url = NSURL::from(ACCESSIBILITY_SETTINGS_URL); - let shared: IdRef = msg_send![class!(NSWorkspace), sharedWorkspace]; - let _: () = msg_send![*shared, openURL: url]; +pub fn open_accessibility() -> bool { + let string = ns_string!(ACCESSIBILITY_SETTINGS_URL); + let url = unsafe { NSURL::initWithString(NSURL::alloc(), string) }; + if let Some(url) = url { + let workspace = unsafe { NSWorkspace::sharedWorkspace() }; + unsafe { workspace.openURL(&url) } + } else { + false } } diff --git a/crates/macos-utils/src/applications.rs b/crates/macos-utils/src/applications.rs index 33442a8d3..619d01ce2 100644 --- a/crates/macos-utils/src/applications.rs +++ b/crates/macos-utils/src/applications.rs @@ -1,25 +1,15 @@ -use appkit_nsworkspace_bindings::{ - INSRunningApplication, - INSURL, - INSWorkspace, - NSRunningApplication, - NSURL, - NSWorkspace, - NSWorkspace_NSWorkspaceRunningApplications, -}; - -use crate::{ - NSArrayRef, +use objc2_app_kit::NSWorkspace; +use objc2_foundation::{ NSString, - NSStringRef, + NSURL, }; #[derive(Debug)] pub struct MacOSApplication { pub name: Option, pub bundle_identifier: Option, - pub process_identifier: i32, pub bundle_path: Option, + pub process_identifier: libc::pid_t, } impl MacOSApplication { @@ -37,21 +27,19 @@ impl MacOSApplication { pub fn running_applications() -> Vec { unsafe { let workspace = NSWorkspace::sharedWorkspace(); - - let apps: NSArrayRef = workspace.runningApplications().into(); - apps.into_iter() + let apps = workspace.runningApplications(); + apps.iter() .map(|app| { - let application = NSRunningApplication(*app as *mut _); - - let name = NSStringRef::new(application.localizedName().0); - let bundle_id = NSStringRef::new(application.bundleIdentifier().0); - let bundle_path = NSStringRef::new(application.bundleURL().path().0); + let name = app.localizedName().map(|s| s.to_string()); + let bundle_identifier = app.bundleIdentifier().map(|s| s.to_string()); + let bundle_path = app.bundleURL().and_then(|url| url.path()).map(|s| s.to_string()); + let process_identifier = app.processIdentifier(); MacOSApplication { - name: name.as_str().map(|s| s.to_string()), - bundle_identifier: bundle_id.as_str().map(|s| s.to_string()), - bundle_path: bundle_path.as_str().map(|s| s.to_string()), - process_identifier: application.processIdentifier(), + name, + bundle_identifier, + bundle_path, + process_identifier, } }) .collect() @@ -61,33 +49,30 @@ pub fn running_applications() -> Vec { pub fn running_applications_matching(bundle_identifier: &str) -> Vec { running_applications() .into_iter() - .filter_map(|app| { - // todo: use `and_then` for more functional approach - if matches!(&app.bundle_identifier, Some(bundle_id) if bundle_id.as_str() == bundle_identifier) { - return Some(app); - } - - None - }) + .filter(|app| matches!(&app.bundle_identifier, Some(bundle_id) if bundle_id.as_str() == bundle_identifier)) .collect() } pub fn launch_application(bundle_path: &str) { - unsafe { - let workspace = NSWorkspace::sharedWorkspace(); + let bundle_nsstring = NSString::from_str(bundle_path); + let bundle_nsurl = unsafe { NSURL::fileURLWithPath_isDirectory(&bundle_nsstring, true) }; + + let workspace = unsafe { NSWorkspace::sharedWorkspace() }; + unsafe { workspace.openURL(&bundle_nsurl) }; +} - let str: NSString = bundle_path.into(); - let url = NSURL::fileURLWithPath_isDirectory_(str.to_appkit_nsstring(), objc::runtime::YES); +#[cfg(test)] +mod tests { + use super::*; - workspace.openURL_(url); + #[test] + fn test_running_applications() { + let applications = running_applications(); + println!("{:#?}", applications); } } -// #[test] -// fn test() { -// // let out = dbg!(running_applications()); -// launch_application("/Applications/Alacritty.app") -// } +// launch_application("/Applications/Alacritty.app") // #[test] // fn test_terminate() { diff --git a/crates/macos-utils/src/bundle.rs b/crates/macos-utils/src/bundle.rs index ae11031b3..9c7d4250e 100644 --- a/crates/macos-utils/src/bundle.rs +++ b/crates/macos-utils/src/bundle.rs @@ -1,22 +1,12 @@ use std::path::PathBuf; -use core_foundation::base::TCFType; -use core_foundation::bundle::{ - CFBundleCopyBundleURL, - CFBundleGetMainBundle, -}; -use core_foundation::url::{ - CFURL, - CFURLRef, -}; +use objc2_foundation::NSBundle; pub fn get_bundle_path() -> Option { - let url: CFURLRef = unsafe { CFBundleCopyBundleURL(CFBundleGetMainBundle()) }; - if url.is_null() { - return None; - } - let url = unsafe { CFURL::wrap_under_get_rule(url) }; - url.to_path() + let main = NSBundle::mainBundle(); + let url = unsafe { main.bundleURL() }; + let path = unsafe { url.path() }?; + Some(path.to_string().into()) } pub fn get_bundle_path_for_executable(executable: &str) -> Option { diff --git a/crates/macos-utils/src/image.rs b/crates/macos-utils/src/image.rs index 5bf6cc11c..a71741c5a 100644 --- a/crates/macos-utils/src/image.rs +++ b/crates/macos-utils/src/image.rs @@ -1,107 +1,85 @@ use std::path::Path; -use appkit_nsworkspace_bindings::{ - INSWorkspace, - NSData, - NSFileTypeDirectory, - NSUInteger, +use objc2::ClassType; +use objc2::rc::Retained; +use objc2_app_kit::{ + NSBitmapImageFileType, + NSBitmapImageRep, + NSGraphicsContext, + NSImage, NSWorkspace, - NSWorkspace_NSDeprecated, -}; -use cocoa::appkit::NSImage; -use cocoa::base::{ - id, - nil as NIL, }; -use cocoa::foundation::{ +use objc2_foundation::{ + NSDictionary, + NSFileTypeDirectory, NSPoint, NSRect, NSSize, -}; -use objc::rc::autoreleasepool; -use objc::runtime::Object; -use objc::{ - class, - msg_send, - sel, - sel_impl, + NSString, }; -use super::util::NSString; - -const PNG_REPRESENTATION: u64 = 4; - #[allow(clippy::missing_safety_doc)] -unsafe fn resize_image(image: id, size: NSSize) -> Option { +unsafe fn resize_image(image: &NSImage, size: NSSize) -> Option> { let rect = NSRect { origin: NSPoint { x: 0.0, y: 0.0 }, size, }; - let ns_graphics = class!(NSGraphicsContext); - let context: id = msg_send![ns_graphics, currentContext]; - let rep = image.bestRepresentationForRect_context_hints_(rect, context, NIL); - - if rep == NIL { - return None; - }; + let context = NSGraphicsContext::currentContext(); + let rep = image.bestRepresentationForRect_context_hints(rect, context.as_deref(), None)?; - let ns_image_cls = class!(NSImage); - let image: id = msg_send![ns_image_cls, alloc]; - let image: id = msg_send![image, initWithSize: size]; - image.addRepresentation_(rep); + let image = NSImage::initWithSize(NSImage::alloc(), size); + image.addRepresentation(&rep); Some(image) } #[allow(clippy::missing_safety_doc)] pub unsafe fn png_for_name(name: &str) -> Option> { - autoreleasepool(|| { - let shared = NSWorkspace::sharedWorkspace(); - let image = shared.iconForFileType_(NSString::from(name).to_appkit_nsstring()).0; - convert_image(image) - }) + let shared = NSWorkspace::sharedWorkspace(); + + let file_type = NSString::from_str(name); + + #[allow(deprecated, reason = "iconForContentType is not available in objc2")] + let image = shared.iconForFileType(&file_type); + + convert_image(&image) } #[allow(clippy::missing_safety_doc)] pub unsafe fn png_for_path(path: &Path) -> Option> { - autoreleasepool(|| { - let shared = NSWorkspace::sharedWorkspace(); - let image = if path.exists() { - let file_path: NSString = path.to_str()?.into(); - shared.iconForFile_(file_path.to_appkit_nsstring()).0 + let shared = NSWorkspace::sharedWorkspace(); + let image = if path.exists() { + let file_path = NSString::from_str(path.to_str()?); + shared.iconForFile(&file_path) + } else { + let is_dir = std::fs::metadata(path).ok().map(|meta| meta.is_dir()).unwrap_or(false); + let file_type = if is_dir { + NSFileTypeDirectory } else { - let is_dir = std::fs::metadata(path).ok().map(|meta| meta.is_dir()).unwrap_or(false); - let file_type: NSString = if is_dir { - NSFileTypeDirectory.into() - } else { - path.extension()?.to_str()?.into() - }; - shared.iconForFileType_(file_type.to_appkit_nsstring()).0 + &NSString::from_str(path.extension()?.to_str()?) }; - convert_image(image) - }) -} + #[allow(deprecated, reason = "iconForContentType is not available in objc2")] + shared.iconForFileType(file_type) + }; -unsafe fn convert_image(image: *mut Object) -> Option> { - let image = resize_image(image, NSSize { - width: 32.0, - height: 32.0, - })?; - let tiff_data = image.TIFFRepresentation(); + convert_image(&image) +} - let ns_bitmap_cls = class!(NSBitmapImageRep); - let image_rep: id = msg_send![ns_bitmap_cls, imageRepWithData: tiff_data]; +fn convert_image(image: &NSImage) -> Option> { + let image = unsafe { + resize_image(image, NSSize { + width: 32.0, + height: 32.0, + }) + }?; + let tiff_data = unsafe { image.TIFFRepresentation() }?; - let png_data: NSData = msg_send![ - image_rep, - representationUsingType: PNG_REPRESENTATION properties: NIL - ]; + let image_rep = unsafe { NSBitmapImageRep::imageRepWithData(&tiff_data) }?; - let len: NSUInteger = msg_send![png_data, length]; - let bytes: *const u8 = msg_send![png_data, bytes]; - let slice = std::slice::from_raw_parts(bytes, len as usize); + let properties = NSDictionary::new(); + let png_data = unsafe { image_rep.representationUsingType_properties(NSBitmapImageFileType::PNG, &properties) }?; - Some(slice.into()) + Some(png_data.bytes().into()) } diff --git a/crates/macos-utils/src/lib.rs b/crates/macos-utils/src/lib.rs index 838a4808f..ede2b9dbb 100644 --- a/crates/macos-utils/src/lib.rs +++ b/crates/macos-utils/src/lib.rs @@ -1,9 +1,4 @@ #![cfg(target_os = "macos")] -// This is needed for objc -#![allow(unexpected_cfgs)] - -#[macro_use] -extern crate objc; pub mod accessibility; pub mod applications; @@ -16,11 +11,6 @@ mod util; pub mod window_server; pub use util::{ - NSArray, - NSArrayRef, - NSString, - NSStringRef, - NSURL, NotificationCenter, get_user_info_from_notification, }; diff --git a/crates/macos-utils/src/os.rs b/crates/macos-utils/src/os.rs index b32b58bb4..50c738cfb 100644 --- a/crates/macos-utils/src/os.rs +++ b/crates/macos-utils/src/os.rs @@ -1,26 +1,32 @@ -use cocoa::base::id; -use cocoa::foundation::NSInteger; - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct NSOperatingSystemVersion { - pub major: NSInteger, - pub minor: NSInteger, - pub patch: NSInteger, -} +use objc2_foundation::{ + NSOperatingSystemVersion, + NSProcessInfo, +}; + +#[derive(Clone, Copy, Debug)] +pub struct OperatingSystemVersion(NSOperatingSystemVersion); -impl NSOperatingSystemVersion { +impl OperatingSystemVersion { pub fn get() -> Self { - unsafe { - let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; - msg_send![process_info, operatingSystemVersion] - } + Self(NSProcessInfo::processInfo().operatingSystemVersion()) + } + + pub fn major(&self) -> isize { + self.0.majorVersion + } + + pub fn minor(&self) -> isize { + self.0.minorVersion + } + + pub fn patch(&self) -> isize { + self.0.patchVersion } } -impl std::fmt::Display for NSOperatingSystemVersion { +impl std::fmt::Display for OperatingSystemVersion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + write!(f, "{}.{}.{}", self.major(), self.minor(), self.patch()) } } @@ -30,10 +36,11 @@ mod tests { #[test] fn test_operating_system_version() { - let v = NSOperatingSystemVersion::get(); - assert!(v.major >= 10); + let v = OperatingSystemVersion::get(); + assert!(v.major() >= 10); let formatted = format!("{v}"); + println!("version: {formatted}"); assert!(formatted.contains('.')); } } diff --git a/crates/macos-utils/src/url.rs b/crates/macos-utils/src/url.rs index 7dc90319a..b2c9c36f3 100644 --- a/crates/macos-utils/src/url.rs +++ b/crates/macos-utils/src/url.rs @@ -1,25 +1,12 @@ -use std::path::{ - Path, - PathBuf, -}; +use std::path::PathBuf; -use appkit_nsworkspace_bindings::{ - INSURL, - INSWorkspace, - NSWorkspace, -}; - -use crate::{ - NSString, - NSStringRef, -}; +use objc2_app_kit::NSWorkspace; +use objc2_foundation::NSString; pub fn path_for_application(bundle_identifier: &str) -> Option { - let bundle_identifier: NSString = bundle_identifier.into(); - let url = unsafe { - NSWorkspace::sharedWorkspace().URLForApplicationWithBundleIdentifier_(bundle_identifier.to_appkit_nsstring()) - }; - let path = unsafe { url.path() }; - let reference = unsafe { NSStringRef::new(path.0) }; - reference.as_str().map(|x| Path::new(x).to_path_buf()) + let bundle_identifier = NSString::from_str(bundle_identifier); + let workspace = unsafe { NSWorkspace::sharedWorkspace() }; + let url = unsafe { workspace.URLForApplicationWithBundleIdentifier(&bundle_identifier) }?; + let path = unsafe { url.path() }?; + Some(path.to_string().into()) } diff --git a/crates/macos-utils/src/util/mod.rs b/crates/macos-utils/src/util/mod.rs index dadba3aa4..2bfa66873 100644 --- a/crates/macos-utils/src/util/mod.rs +++ b/crates/macos-utils/src/util/mod.rs @@ -1,7 +1,6 @@ pub mod notification_center; mod nsarray; mod nsstring; -mod nsurl; use std::ops::Deref; @@ -10,15 +9,8 @@ pub use notification_center::{ NotificationCenter, get_user_info_from_notification, }; -pub use nsarray::{ - NSArray, - NSArrayRef, -}; -pub use nsstring::{ - NSString, - NSStringRef, -}; -pub use nsurl::NSURL; +pub use nsarray::NSArrayRef; +pub use nsstring::NSStringRef; use objc::rc::StrongPtr; use objc::runtime::Object; @@ -30,10 +22,6 @@ impl Id { pub unsafe fn new(ptr: *mut Object) -> Self { Self(StrongPtr::new(ptr)) } - - pub fn autorelease(self) -> *mut Object { - self.0.autorelease() - } } impl std::ops::Deref for Id { @@ -60,14 +48,6 @@ impl IdRef { pub fn is_nil(&self) -> bool { self.0 == nil } - - /// # Safety - /// - /// This is unsafe because the caller must ensure that the pointer has exclusive - /// access to the object. - pub unsafe fn as_mut_ptr(&self) -> *mut Object { - self.0 as *mut _ - } } impl Deref for IdRef { diff --git a/crates/macos-utils/src/util/notification_center.rs b/crates/macos-utils/src/util/notification_center.rs index 1d684c2e5..b99110e8c 100644 --- a/crates/macos-utils/src/util/notification_center.rs +++ b/crates/macos-utils/src/util/notification_center.rs @@ -1,133 +1,122 @@ -use appkit_nsworkspace_bindings::{ - INSNotification, - INSNotificationCenter, - INSWorkspace, - NSNotification, - NSNotificationCenter, +use std::ptr::NonNull; + +use objc2::rc::Retained; +use objc2::runtime::{ + AnyObject, + Sel, +}; +use objc2_app_kit::{ NSRunningApplication, NSWorkspace, }; -use block; -use cocoa::base::{ - id, - nil as NIL, +use objc2_foundation::{ + NSDictionary, + NSDistributedNotificationCenter, + NSNotification, + NSNotificationCenter, + NSNotificationName, + NSOperationQueue, + ns_string, }; -use cocoa::foundation::NSDictionary; -use objc::runtime::Object; -use super::NSString; +enum Inner { + NotificationCenter(Retained), + DistributedNotificationCenter(Retained), +} pub struct NotificationCenter { - inner: NSNotificationCenter, + inner: Inner, } impl NotificationCenter { - pub fn new(center: NSNotificationCenter) -> Self { - Self { inner: center } + pub fn new(center: Retained) -> Self { + Self { + inner: Inner::NotificationCenter(center), + } } pub fn workspace_center() -> Self { - let shared = unsafe { NSWorkspace::sharedWorkspace().notificationCenter() }; - Self::new(shared) + let center = unsafe { NSWorkspace::sharedWorkspace().notificationCenter() }; + Self::new(center) } pub fn default_center() -> Self { - let default = unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] }; - Self::new(default) + let center = unsafe { NSNotificationCenter::defaultCenter() }; + Self::new(center) } pub fn distributed_center() -> Self { - let distributed_default: *mut Object = - unsafe { msg_send![class!(NSDistributedNotificationCenter), defaultCenter] }; - Self::new(appkit_nsworkspace_bindings::NSNotificationCenter(distributed_default)) + let center = unsafe { NSDistributedNotificationCenter::defaultCenter() }; + Self { + inner: Inner::DistributedNotificationCenter(center), + } } #[allow(clippy::missing_safety_doc)] - pub unsafe fn remove_observer(&self, observer: id) { - self.inner.removeObserver_(observer); + pub unsafe fn remove_observer(&self, observer: &AnyObject) { + match &self.inner { + Inner::NotificationCenter(i) => i.removeObserver(observer), + Inner::DistributedNotificationCenter(i) => i.removeObserver(observer), + } } - pub fn post_notification(&self, notification_name: impl Into, info: I) - where - I: IntoIterator, - K: Into, - V: Into, - { - let name: NSString = notification_name.into(); + pub fn post_notification(&self, notification_name: &NSNotificationName, user_info: &NSDictionary) { unsafe { - let (keys, objs): (Vec, Vec) = info - .into_iter() - .map(|(k, v)| (k.into().into_inner().autorelease(), v.into().into_inner().autorelease())) - .unzip(); - - use cocoa::foundation as cf; - let keys_array = cf::NSArray::arrayWithObjects(NIL, &keys); - let objs_array = cf::NSArray::arrayWithObjects(NIL, &objs); - - let user_info = NSDictionary::dictionaryWithObjects_forKeys_(NIL, objs_array, keys_array); - - self.inner.postNotificationName_object_userInfo_( - name.to_appkit_nsstring(), - NIL, - appkit_nsworkspace_bindings::NSDictionary(user_info), - ); + match &self.inner { + Inner::NotificationCenter(i) => { + i.postNotificationName_object_userInfo(notification_name, None, Some(user_info)) + }, + Inner::DistributedNotificationCenter(i) => { + i.postNotificationName_object_userInfo(notification_name, None, Some(user_info)) + }, + } } } #[allow(clippy::missing_safety_doc)] pub unsafe fn subscribe_with_observer( &mut self, - notification_name: impl Into, - observer: id, - callback: objc::runtime::Sel, + notification_name: &NSNotificationName, + observer: &AnyObject, + callback: Sel, ) { - let name: NSString = notification_name.into(); - self.inner - .addObserver_selector_name_object_(observer, callback, name.to_appkit_nsstring(), NIL); + match &self.inner { + Inner::NotificationCenter(i) => { + i.addObserver_selector_name_object(observer, callback, Some(notification_name), None); + }, + Inner::DistributedNotificationCenter(i) => { + i.addObserver_selector_name_object(observer, callback, Some(notification_name), None); + }, + } } - pub fn subscribe(&mut self, notification_name: impl Into, queue: Option, f: F) + pub fn subscribe(&mut self, notification_name: &NSNotificationName, queue: Option<&NSOperationQueue>, f: F) where - F: Fn(NSNotification), + F: Fn(NonNull) + Clone + 'static, { - let mut block = block::ConcreteBlock::new(f); + let block = block2::StackBlock::new(f); + // addObserverForName copies block for us. unsafe { - let name: NSString = notification_name.into(); - // addObserverForName copies block for us. - self.inner.addObserverForName_object_queue_usingBlock_( - name.to_appkit_nsstring(), - NIL, - appkit_nsworkspace_bindings::NSOperationQueue(queue.unwrap_or(NIL)), - &mut block as *mut _ as *mut std::os::raw::c_void, - ); + match &self.inner { + Inner::NotificationCenter(i) => { + i.addObserverForName_object_queue_usingBlock(Some(notification_name), None, queue, &block); + }, + Inner::DistributedNotificationCenter(i) => { + i.addObserverForName_object_queue_usingBlock(Some(notification_name), None, queue, &block); + }, + } } } } -pub unsafe fn get_app_from_notification(notification: &NSNotification) -> Option { - let user_info = notification.userInfo().0; - - if user_info.is_null() { - return None; - } - - let bundle_id_str: NSString = "NSWorkspaceApplicationKey".into(); - - let app = user_info.objectForKey_(***bundle_id_str); - if app.is_null() { - None - } else { - Some(NSRunningApplication(app)) - } +pub unsafe fn get_app_from_notification(notification: &NSNotification) -> Option> { + let user_info = notification.userInfo()?; + let bundle_id_str = ns_string!("NSWorkspaceApplicationKey"); + let app = user_info.objectForKey(bundle_id_str); + app.map(|app| Retained::::cast(app)) } #[allow(clippy::missing_safety_doc)] -pub unsafe fn get_user_info_from_notification(notification: &NSNotification) -> Option { - let user_info = notification.userInfo().0; - - if user_info.is_null() { - return None; - } - - Some(user_info) +pub unsafe fn get_user_info_from_notification(notification: &NSNotification) -> Option> { + notification.userInfo() } diff --git a/crates/macos-utils/src/util/nsstring.rs b/crates/macos-utils/src/util/nsstring.rs index e1745060e..39472f1ec 100644 --- a/crates/macos-utils/src/util/nsstring.rs +++ b/crates/macos-utils/src/util/nsstring.rs @@ -1,65 +1,8 @@ use appkit_nsworkspace_bindings::NSString as AppkitNSString; -use cocoa::base::nil as NIL; use cocoa::foundation::NSString as CocoaNSString; use objc::runtime::Object; -use super::{ - Id, - IdRef, -}; - -/// This is an owned NSString -#[repr(transparent)] -pub struct NSString(Id); - -impl NSString { - #[allow(clippy::missing_safety_doc)] - pub unsafe fn new(raw: *mut Object) -> Self { - Self(Id::new(raw)) - } - - pub fn into_inner(self) -> Id { - self.0 - } - - pub fn to_appkit_nsstring(self) -> AppkitNSString { - AppkitNSString(self.0.autorelease()) - } - - pub fn as_str(&self) -> Option<&str> { - if self.is_null() { - None - } else { - unsafe { - let bytes: *const std::os::raw::c_char = self.UTF8String(); - let len = self.len(); - let bytes = std::slice::from_raw_parts(bytes as *const u8, len); - Some(std::str::from_utf8_unchecked(bytes)) - } - } - } -} - -impl From for NSString { - fn from(s: AppkitNSString) -> Self { - Self(unsafe { Id::new(s.0) }) - } -} - -impl From<&str> for NSString { - fn from(s: &str) -> Self { - let inner = unsafe { CocoaNSString::alloc(NIL).init_str(s) }; - Self(unsafe { Id::new(inner) }) - } -} - -impl std::ops::Deref for NSString { - type Target = Id; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} +use super::IdRef; /// This is a borrowed NSString #[repr(transparent)] diff --git a/crates/macos-utils/src/util/nsurl.rs b/crates/macos-utils/src/util/nsurl.rs deleted file mode 100644 index 7209a913a..000000000 --- a/crates/macos-utils/src/util/nsurl.rs +++ /dev/null @@ -1,30 +0,0 @@ -use cocoa::base::nil; -use cocoa::foundation::NSURL as CocoaNSURL; - -use super::Id; -use crate::NSString; - -/// An owned NSURL -#[repr(transparent)] -#[derive(Clone)] -pub struct NSURL(Id); - -impl From for NSURL -where - S: Into, -{ - fn from(s: S) -> Self { - let string: NSString = s.into(); - let nsurl = unsafe { CocoaNSURL::alloc(nil).initWithString_(***string) }; - assert!(!nsurl.is_null()); - Self(unsafe { Id::new(nsurl) }) - } -} - -impl std::ops::Deref for NSURL { - type Target = Id; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/crates/macos-utils/src/window_server/mod.rs b/crates/macos-utils/src/window_server/mod.rs index 2753b1c3e..2c1fb76be 100644 --- a/crates/macos-utils/src/window_server/mod.rs +++ b/crates/macos-utils/src/window_server/mod.rs @@ -23,25 +23,7 @@ use accessibility_sys::{ kAXWindowResizedNotification, pid_t, }; -use appkit_nsworkspace_bindings::{ - INSNotification, - INSRunningApplication, - INSWorkspace, - NSApplicationActivationPolicy_NSApplicationActivationPolicyProhibited as ActivationPolicy_Prohibited, - NSNotification, - NSRunningApplication, - NSWorkspace, - NSWorkspace_NSWorkspaceRunningApplications, - NSWorkspaceActiveSpaceDidChangeNotification, - NSWorkspaceDidActivateApplicationNotification, - NSWorkspaceDidLaunchApplicationNotification, - NSWorkspaceDidTerminateApplicationNotification, -}; use ax_observer::AXObserver; -use cocoa::base::{ - id, - nil, -}; use core_foundation::base::TCFType; use core_foundation::string::{ CFString, @@ -49,12 +31,31 @@ use core_foundation::string::{ }; use dashmap::DashMap; use flume::Sender; -use objc::declare::ClassDecl; -use objc::runtime::{ - Class, - Object, - Sel, - objc_getClass, +use objc2::rc::{ + Allocated, + Retained, +}; +use objc2::runtime::AnyObject; +use objc2::{ + ClassType, + DeclaredClass, + declare_class, + msg_send_id, + mutability, + sel, +}; +use objc2_app_kit::{ + NSApplicationActivationPolicy, + NSRunningApplication, + NSWorkspace, + NSWorkspaceActiveSpaceDidChangeNotification, + NSWorkspaceDidActivateApplicationNotification, + NSWorkspaceDidLaunchApplicationNotification, + NSWorkspaceDidTerminateApplicationNotification, +}; +use objc2_foundation::{ + NSNotification, + NSObject, }; use tracing::{ debug, @@ -68,13 +69,8 @@ pub use ui_element::{ UIElement, }; -use super::util::notification_center::get_app_from_notification; -use super::util::{ - NSArrayRef, - NotificationCenter, -}; -use crate::NSStringRef; -use crate::util::Id; +use crate::util::NotificationCenter; +use crate::util::notification_center::get_app_from_notification; const BLOCKED_BUNDLE_IDS: &[&str] = &[ "com.apple.ViewBridgeAuxiliary", @@ -133,16 +129,12 @@ pub struct AccessibilityCallbackData { } unsafe fn app_bundle_id(app: &NSRunningApplication) -> Option { - if matches!(app, NSRunningApplication(nil)) { - return None; - } - let bundle_id = NSStringRef::new(app.bundleIdentifier().0); - bundle_id.as_str().map(|s| s.into()) + app.bundleIdentifier().map(|s| s.to_string()) } pub struct WindowServer { _inner: Pin>, - observer: Id, + observer: Retained, } // SAFETY: observer id pointer is send + sync safe @@ -165,25 +157,25 @@ impl WindowServer { unsafe { center.subscribe_with_observer( NSWorkspaceActiveSpaceDidChangeNotification, - **observer, + &observer, sel!(activeSpaceChanged:), ); center.subscribe_with_observer( NSWorkspaceDidLaunchApplicationNotification, - **observer, + &observer, sel!(didLaunchApplication:), ); center.subscribe_with_observer( NSWorkspaceDidTerminateApplicationNotification, - **observer, + &observer, sel!(didTerminateApplication:), ); center.subscribe_with_observer( NSWorkspaceDidActivateApplicationNotification, - **observer, + &observer, sel!(didActivateApplication:), ); } @@ -200,129 +192,108 @@ impl Drop for WindowServer { fn drop(&mut self) { let center = NotificationCenter::workspace_center(); unsafe { - center.remove_observer(**self.observer); + center.remove_observer(&self.observer); } } } trait WindowServerHandler { - fn did_activate_application(&mut self, notif: NSNotification); - fn active_space_changed(&mut self, notif: NSNotification); - fn did_terminate_application(&mut self, notif: NSNotification); - fn did_launch_application(&mut self, notif: NSNotification); + fn did_activate_application(&mut self, notif: &NSNotification); + fn active_space_changed(&mut self, notif: &NSNotification); + fn did_terminate_application(&mut self, notif: &NSNotification); + fn did_launch_application(&mut self, notif: &NSNotification); } const OBSERVER_CLASS_NAME: &str = "CodeWhisperer_WindowServerObserver"; -impl WindowServerInner { - pub fn new(sender: Sender) -> Self { - Self { - observers: Default::default(), - sender, - } - } +pub struct Ivars { + handler: *mut c_void, +} - fn register_observer_class() -> *const Class { - let name = std::ffi::CString::new(OBSERVER_CLASS_NAME).unwrap(); - let existing_class = unsafe { objc_getClass(name.as_ptr()) }; - if existing_class.is_null() { - // TODO: this logic/the above trait could easily be generated by a macro. - let mut decl = ClassDecl::new(OBSERVER_CLASS_NAME, class!(NSObject)).unwrap(); - unsafe { - decl.add_ivar::<*mut c_void>("handler"); - - extern "C" fn did_activate_application(this: &Object, _: Sel, notif: id) { - unsafe { - let inner = *this.get_ivar::<*mut c_void>("handler") as *mut WindowServerInner; - let inner = <*mut WindowServerInner>::as_mut(inner).unwrap(); - inner.did_activate_application(NSNotification(notif)); - } - } +declare_class! { + pub struct ObserverClass; - decl.add_method( - sel!(didActivateApplication:), - did_activate_application as extern "C" fn(&Object, Sel, id), - ); + unsafe impl ClassType for ObserverClass { + type Super = NSObject; + type Mutability = mutability::InteriorMutable; + const NAME: &'static str = OBSERVER_CLASS_NAME; + } - extern "C" fn active_space_changed(this: &Object, _: Sel, notif: id) { - unsafe { - let inner = *this.get_ivar::<*mut c_void>("handler") as *mut WindowServerInner; - let inner = <*mut WindowServerInner>::as_mut(inner).unwrap(); - inner.active_space_changed(NSNotification(notif)); - } - } + impl DeclaredClass for ObserverClass { + type Ivars = Ivars; + } - decl.add_method( - sel!(activeSpaceChanged:), - active_space_changed as extern "C" fn(&Object, Sel, id), - ); + unsafe impl ObserverClass { + #[method_id(initWithHandler:)] + fn init_with_handler(this: Allocated, handler: *mut c_void) -> Option> { + let this = this.set_ivars(Ivars { + handler + }); + unsafe { msg_send_id![super(this), init] } + } - extern "C" fn did_terminate_application(this: &Object, _: Sel, notif: id) { - unsafe { - let inner = *this.get_ivar::<*mut c_void>("handler") as *mut WindowServerInner; - let inner = <*mut WindowServerInner>::as_mut(inner).unwrap(); - inner.did_terminate_application(NSNotification(notif)); - } - } + #[method(didActivateApplication:)] + fn did_activate_application(&self, notif: &NSNotification) { + let inner = self.ivars().handler as *mut WindowServerInner; + let inner = unsafe { &mut *inner }; + inner.did_activate_application(notif); + } - decl.add_method( - sel!(didTerminateApplication:), - did_terminate_application as extern "C" fn(&Object, Sel, id), - ); + #[method(activeSpaceChanged:)] + fn active_space_changed(&self, notif: &NSNotification) { + let inner = self.ivars().handler as *mut WindowServerInner; + let inner = unsafe { &mut *inner }; + inner.active_space_changed(notif); + } - extern "C" fn did_launch_application(this: &Object, _: Sel, notif: id) { - unsafe { - let inner = *this.get_ivar::<*mut c_void>("handler") as *mut WindowServerInner; - let inner = <*mut WindowServerInner>::as_mut(inner).unwrap(); - inner.did_launch_application(NSNotification(notif)); - } - } + #[method(didTerminateApplication:)] + fn did_terminate_application(&self, notif: &NSNotification) { + let inner = self.ivars().handler as *mut WindowServerInner; + let inner = unsafe { &mut *inner }; + inner.did_terminate_application(notif); + } - decl.add_method( - sel!(didLaunchApplication:), - did_launch_application as extern "C" fn(&Object, Sel, id), - ); + #[method(didLaunchApplication:)] + fn did_launch_application(&self, notif: &NSNotification) { + let inner = self.ivars().handler as *mut WindowServerInner; + let inner = unsafe { &mut *inner }; + inner.did_launch_application(notif); + } + } +} - extern "C" fn initialize_with_handler(this: &mut Object, _: Sel, x: *mut c_void) { - unsafe { - (*this).set_ivar::<*mut c_void>("handler", x); - } - } +impl ObserverClass { + pub fn new(handler: *mut c_void) -> Retained { + unsafe { msg_send_id![Self::alloc(), initWithHandler:handler] } + } +} - decl.add_method( - sel!(initializeWithHandler:), - initialize_with_handler as extern "C" fn(&mut Object, Sel, *mut c_void), - ); - } - decl.register() - } else { - existing_class +impl WindowServerInner { + pub fn new(sender: Sender) -> Self { + Self { + observers: Default::default(), + sender, } } - pub fn new_with_observer(sender: Sender) -> (Pin>, Id) { - let cls = WindowServerInner::register_observer_class(); + pub fn new_with_observer(sender: Sender) -> (Pin>, Retained) { let pin = Box::pin(Self { observers: Default::default(), sender, }); - - let observer: id = unsafe { msg_send![cls, alloc] }; - let _: () = unsafe { - let handler = &*pin as *const Self as *mut c_void; - msg_send![observer, initializeWithHandler: handler] - }; - (pin, unsafe { Id::new(observer) }) + let handler = &*pin as *const Self as *mut c_void; + let r = ObserverClass::new(handler); + (pin, r) } #[allow(clippy::missing_safety_doc)] - unsafe fn register(&mut self, ns_app: NSRunningApplication, from_activation: bool) { + unsafe fn register(&mut self, ns_app: &NSRunningApplication, from_activation: bool) { if !AXIsProcessTrusted() { info!("Cannot register to observer window events without accessibility perms"); return; } - let bundle_id = match app_bundle_id(&ns_app) { + let bundle_id = match app_bundle_id(ns_app) { Some(bundle_id) => bundle_id, None => { debug!("Ignoring empty bundle id"); @@ -345,7 +316,7 @@ impl WindowServerInner { } } - if ns_app.activationPolicy() == ActivationPolicy_Prohibited { + if ns_app.activationPolicy() == NSApplicationActivationPolicy::Prohibited { debug!("Ignoring application by activation policy"); return; } @@ -409,12 +380,12 @@ impl WindowServerInner { unsafe { let workspace = NSWorkspace::sharedWorkspace(); - let app = workspace.frontmostApplication(); - self.register(app, true); + if let Some(app) = workspace.frontmostApplication() { + self.register(&app, true); + } - let apps: NSArrayRef = workspace.runningApplications().into(); - for app in apps.iter() { - self.register(NSRunningApplication(*app as *mut _), false) + for app in workspace.runningApplications().iter() { + self.register(app, false) } } @@ -431,20 +402,23 @@ impl WindowServerInner { } impl WindowServerHandler for WindowServerInner { - fn did_activate_application(&mut self, notif: NSNotification) { + fn did_activate_application(&mut self, notif: &NSNotification) { unsafe { - if let Some(app) = get_app_from_notification(¬if) { + if let Some(app) = get_app_from_notification(notif) { let bundle_id = app_bundle_id(&app); trace!("Activated application {bundle_id:?}"); - self.register(app, true); + self.register(&app, true); } } } - fn active_space_changed(&mut self, notif: NSNotification) { + fn active_space_changed(&mut self, notif: &NSNotification) { unsafe { - let workspace = NSWorkspace(notif.object()); - let app = workspace.frontmostApplication(); + let Some(object) = notif.object() else { return }; + let workspace: Retained = Retained::::cast(object); + let Some(app) = workspace.frontmostApplication() else { + return; + }; let pid = app.processIdentifier(); let ax_app = AXUIElementCreateApplication(pid); let app_elem: UIElement = ax_app.into(); @@ -462,19 +436,17 @@ impl WindowServerHandler for WindowServerInner { } } - fn did_terminate_application(&mut self, notif: NSNotification) { + fn did_terminate_application(&mut self, notif: &NSNotification) { unsafe { - if let Some(ns_app) = get_app_from_notification(¬if) { + if let Some(ns_app) = get_app_from_notification(notif) { if let Some(bundle_id) = app_bundle_id(&ns_app) { trace!("Terminated application - {bundle_id:?}"); - let apps: NSArrayRef = - NSWorkspace::sharedWorkspace().runningApplications().into(); + let apps = NSWorkspace::sharedWorkspace().runningApplications(); - let has_running = apps.iter().any(|running| { - let running = NSRunningApplication(*running as *mut _); - app_bundle_id(&running).map(|id| id == bundle_id).unwrap_or(false) - }); + let has_running = apps + .iter() + .any(|running| app_bundle_id(running).map(|id| id == bundle_id).unwrap_or(false)); if !has_running { trace!("Deregistering app {bundle_id:?} since no other instances are running"); @@ -485,12 +457,12 @@ impl WindowServerHandler for WindowServerInner { } } - fn did_launch_application(&mut self, notif: NSNotification) { + fn did_launch_application(&mut self, notif: &NSNotification) { unsafe { - if let Some(app) = get_app_from_notification(¬if) { + if let Some(app) = get_app_from_notification(notif) { let bundle_id = app_bundle_id(&app); trace!("Launched application - {bundle_id:?}"); - self.register(app, true) + self.register(&app, true) } } } diff --git a/crates/q_cli/Cargo.toml b/crates/q_cli/Cargo.toml index c869167a7..561bd869b 100644 --- a/crates/q_cli/Cargo.toml +++ b/crates/q_cli/Cargo.toml @@ -60,6 +60,9 @@ indicatif = "0.17.3" indoc.workspace = true mimalloc.workspace = true objc.workspace = true +objc2 = "0.5.2" +objc2-app-kit = { version = "0.2.2", features = ["NSRunningApplication"] } +objc2-foundation = "0.2.2" once_cell.workspace = true owo-colors = "4.0.0" parking_lot = "0.12.1" diff --git a/crates/q_cli/src/util/desktop.rs b/crates/q_cli/src/util/desktop.rs index 63a70ade1..f6c26cdf1 100644 --- a/crates/q_cli/src/util/desktop.rs +++ b/crates/q_cli/src/util/desktop.rs @@ -24,81 +24,69 @@ pub struct LaunchArgs { pub verbose: bool, } +#[cfg(target_os = "macos")] pub fn desktop_app_running() -> bool { - cfg_if::cfg_if! { - if #[cfg(target_os = "macos")] { - use appkit_nsworkspace_bindings::NSRunningApplication; - use macos_utils::{ - NSArray, - NSString, - }; - use objc::{ - class, - msg_send, - sel, - sel_impl, - }; - use sysinfo::{ - ProcessRefreshKind, - RefreshKind, - System, - }; - use std::ffi::OsString; - - use fig_util::consts::{ - APP_PROCESS_NAME, - APP_BUNDLE_ID - }; - - let bundle_id = NSString::from(APP_BUNDLE_ID); - #[allow(unexpected_cfgs)] - let running_applications: NSArray = unsafe { - msg_send![ - class!(NSRunningApplication), - runningApplicationsWithBundleIdentifier: bundle_id - ] - }; - - if !running_applications.is_empty() { - return true; - } + use std::ffi::OsString; - // Fallback to process name check - let app_process_name= OsString::from(APP_PROCESS_NAME); - let system = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); - let mut processes = system.processes_by_exact_name(&app_process_name); - processes.next().is_some() - } else if #[cfg(target_os = "windows")] { - use crate::consts::APP_PROCESS_NAME; - - let output = match std::process::Command::new("tasklist.exe") - .args(["/NH", "/FI", "IMAGENAME eq fig_desktop.exe"]) - .output() - { - Ok(output) => output, - Err(_) => return false, - }; + use fig_util::consts::{ + APP_BUNDLE_ID, + APP_PROCESS_NAME, + }; + use objc2_app_kit::NSRunningApplication; + use objc2_foundation::ns_string; + use sysinfo::{ + ProcessRefreshKind, + RefreshKind, + System, + }; - match std::str::from_utf8(&output.stdout) { - Ok(result) => result.contains(CODEWHISPERER_DESKTOP_PROCESS_NAME), - Err(_) => false, - } - } else { - use sysinfo::{ - ProcessRefreshKind, - RefreshKind, - System, - }; - use std::ffi::OsString; - - use fig_util::consts::APP_PROCESS_NAME; - - let s = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); - let app_process_name = OsString::from(APP_PROCESS_NAME); - let mut processes = s.processes_by_exact_name(&app_process_name); - processes.next().is_some() - } + let bundle_id = ns_string!(APP_BUNDLE_ID); + let running_applications = unsafe { NSRunningApplication::runningApplicationsWithBundleIdentifier(bundle_id) }; + + if !running_applications.is_empty() { + return true; } + + // Fallback to process name check + let app_process_name = OsString::from(APP_PROCESS_NAME); + let system = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); + let mut processes = system.processes_by_exact_name(&app_process_name); + processes.next().is_some() +} + +#[cfg(target_os = "windows")] +pub fn desktop_app_running() -> bool { + use crate::consts::APP_PROCESS_NAME; + + let output = match std::process::Command::new("tasklist.exe") + .args(["/NH", "/FI", "IMAGENAME eq fig_desktop.exe"]) + .output() + { + Ok(output) => output, + Err(_) => return false, + }; + + match std::str::from_utf8(&output.stdout) { + Ok(result) => result.contains(CODEWHISPERER_DESKTOP_PROCESS_NAME), + Err(_) => false, + } +} + +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +pub fn desktop_app_running() -> bool { + use std::ffi::OsString; + + use fig_util::consts::APP_PROCESS_NAME; + use sysinfo::{ + ProcessRefreshKind, + RefreshKind, + System, + }; + + let s = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); + let app_process_name = OsString::from(APP_PROCESS_NAME); + let mut processes = s.processes_by_exact_name(&app_process_name); + processes.next().is_some() } pub fn launch_fig_desktop(args: LaunchArgs) -> Result<()> { diff --git a/package.json b/package.json index 6dcf8f88b..32a1b3be7 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "preview:autocomplete": "pnpm -C packages/autocomplete preview", "preview:dashboard": "pnpm -C packages/dashboard preview", "clean": "turbo clean --parallel", - "prepare": "husky install", + "prepare": "husky", "precommit": "lint-staged --config .lintstagedrc.mjs" }, "devDependencies": { From 9134fc0e6806200ad8b57d1e431e025e6acd2e7d Mon Sep 17 00:00:00 2001 From: Grant Gurvis Date: Tue, 31 Dec 2024 15:03:00 -0500 Subject: [PATCH 2/4] use workspace dep --- Cargo.lock | 1 - Cargo.toml | 4 ++++ crates/fig_desktop/Cargo.toml | 8 ++++---- crates/fig_input_method/Cargo.toml | 8 ++++---- crates/fig_util/Cargo.toml | 6 +++--- crates/macos-utils/Cargo.toml | 6 +++--- crates/q_cli/Cargo.toml | 7 +++---- 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 465a4757d..2a39af7da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6759,7 +6759,6 @@ dependencies = [ "macos-utils", "mimalloc", "nix 0.29.0", - "objc", "objc2", "objc2-app-kit", "objc2-foundation", diff --git a/Cargo.toml b/Cargo.toml index a4331df69..8deb9fc99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,10 @@ nix = { version = "0.29.0", features = [ "user", ] } objc = "0.2.7" +objc2 = "0.5.2" +objc2-app-kit = "0.2.2" +objc2-foundation = "0.2.2" +objc2-input-method-kit = "0.2.2" once_cell = { version = "1.18.0", features = ["parking_lot"] } percent-encoding = "2.2.0" portable-pty = "0.8.1" diff --git a/crates/fig_desktop/Cargo.toml b/crates/fig_desktop/Cargo.toml index 83847e03f..9176ad131 100644 --- a/crates/fig_desktop/Cargo.toml +++ b/crates/fig_desktop/Cargo.toml @@ -135,10 +135,10 @@ core-foundation.workspace = true core-graphics.workspace = true libc.workspace = true macos-utils = { path = "../macos-utils" } -objc = "0.2.7" -objc2 = "0.5.2" -objc2-app-kit = { version = "0.2.2", features = ["NSApplication"] } -objc2-foundation = "0.2.2" +objc.workspace = true +objc2.workspace = true +objc2-app-kit = { workspace = true, features = ["NSApplication"] } +objc2-foundation.workspace = true system-configuration = "0.6.0" [build-dependencies] diff --git a/crates/fig_input_method/Cargo.toml b/crates/fig_input_method/Cargo.toml index 4ffbbed69..9d4bc82ab 100644 --- a/crates/fig_input_method/Cargo.toml +++ b/crates/fig_input_method/Cargo.toml @@ -29,13 +29,13 @@ fig_log.workspace = true fig_proto.workspace = true fig_util.workspace = true macos-utils = { path = "../macos-utils" } -objc2 = "0.5.2" -objc2-app-kit = { version = "0.2.2", features = [ +objc2.workspace = true +objc2-app-kit = { workspace = true, features = [ "NSApplication", "NSResponder", ] } -objc2-foundation = { version = "0.2.2", features = ["NSThread"] } -objc2-input-method-kit = { version = "0.2.2", features = [ +objc2-foundation = { workspace = true, features = ["NSThread"] } +objc2-input-method-kit = { workspace = true, features = [ "IMKServer", "IMKInputController", ] } diff --git a/crates/fig_util/Cargo.toml b/crates/fig_util/Cargo.toml index 8dd0822ad..14eb669e1 100644 --- a/crates/fig_util/Cargo.toml +++ b/crates/fig_util/Cargo.toml @@ -23,7 +23,6 @@ fig_os_shim.workspace = true hex.workspace = true indoc.workspace = true libc.workspace = true -objc2-foundation = { version = "0.2.2", features = ["NSURL"] } paste = "1.0.11" rand.workspace = true regex.workspace = true @@ -41,8 +40,9 @@ whoami.workspace = true appkit-nsworkspace-bindings.workspace = true core-foundation = "0.10.0" macos-utils = { path = "../macos-utils" } -objc2 = "0.5.2" -objc2-app-kit = { version = "0.2.2", features = ["NSWorkspace"] } +objc2.workspace = true +objc2-app-kit = { workspace = true, features = ["NSWorkspace"] } +objc2-foundation = { workspace = true, features = ["NSURL"] } [target.'cfg(target_os = "linux")'.dependencies] bstr.workspace = true diff --git a/crates/macos-utils/Cargo.toml b/crates/macos-utils/Cargo.toml index 1e5e3ac14..f8c2306c6 100644 --- a/crates/macos-utils/Cargo.toml +++ b/crates/macos-utils/Cargo.toml @@ -14,8 +14,8 @@ core-foundation.workspace = true cocoa.workspace = true libc.workspace = true objc.workspace = true -objc2 = "0.5.2" -objc2-app-kit = { version = "0.2.2", features = [ +objc2.workspace = true +objc2-app-kit = { workspace = true, features = [ "NSBitmapImageRep", "NSGraphicsContext", "NSImage", @@ -24,7 +24,7 @@ objc2-app-kit = { version = "0.2.2", features = [ "NSRunningApplication", "libc", ] } -objc2-foundation = { version = "0.2.2", features = [ +objc2-foundation = { workspace = true, features = [ "block2", "NSDictionary", "NSDistributedNotificationCenter", diff --git a/crates/q_cli/Cargo.toml b/crates/q_cli/Cargo.toml index 561bd869b..2dde2537d 100644 --- a/crates/q_cli/Cargo.toml +++ b/crates/q_cli/Cargo.toml @@ -59,10 +59,6 @@ globset = "0.4.10" indicatif = "0.17.3" indoc.workspace = true mimalloc.workspace = true -objc.workspace = true -objc2 = "0.5.2" -objc2-app-kit = { version = "0.2.2", features = ["NSRunningApplication"] } -objc2-foundation = "0.2.2" once_cell.workspace = true owo-colors = "4.0.0" parking_lot = "0.12.1" @@ -93,6 +89,9 @@ nix.workspace = true [target.'cfg(target_os = "macos")'.dependencies] macos-utils = { path = "../macos-utils" } +objc2.workspace = true +objc2-app-kit = { workspace = true, features = ["NSRunningApplication"] } +objc2-foundation.workspace = true [target.'cfg(target_os = "linux")'.dependencies] dbus = { path = "../dbus" } From d79aec5d4ce1fa99b667671b115e47d43409716a Mon Sep 17 00:00:00 2001 From: Grant Gurvis Date: Tue, 31 Dec 2024 16:41:32 -0500 Subject: [PATCH 3/4] fix block2 --- Cargo.lock | 1 - Cargo.toml | 1 + crates/fig_input_method/src/imk.rs | 4 ++-- crates/macos-utils/Cargo.toml | 5 +---- crates/macos-utils/src/window_server/mod.rs | 7 +++++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a39af7da..15365056b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5004,7 +5004,6 @@ dependencies = [ "accessibility", "accessibility-sys", "appkit-nsworkspace-bindings", - "block", "block2", "cocoa", "core-foundation 0.10.0", diff --git a/Cargo.toml b/Cargo.toml index 8deb9fc99..698fed366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ appkit-nsworkspace-bindings = { path = "crates/macos-utils/appkit-nsworkspace-bi async-trait = "0.1.74" aws-types = "1.2.0" base64 = "0.22.1" +block2 = "0.5.1" bytes = "1.5.0" bitflags = { version = "2.6.0", features = ["serde"] } bstr = "1.1.0" diff --git a/crates/fig_input_method/src/imk.rs b/crates/fig_input_method/src/imk.rs index 105e6e656..43814890d 100644 --- a/crates/fig_input_method/src/imk.rs +++ b/crates/fig_input_method/src/imk.rs @@ -56,9 +56,9 @@ struct Ivars { declare_class!( struct MyInputController; - // - The superclass NSObject does not have any subclassing requirements. + // - The superclass IMKInputController does not have any subclassing requirements. // - Interior mutability is a safe default. - // - `MyCustomObject` does not implement `Drop`. + // - `MyInputController` does not implement `Drop`. unsafe impl ClassType for MyInputController { type Super = IMKInputController; type Mutability = InteriorMutable; diff --git a/crates/macos-utils/Cargo.toml b/crates/macos-utils/Cargo.toml index f8c2306c6..8e2109387 100644 --- a/crates/macos-utils/Cargo.toml +++ b/crates/macos-utils/Cargo.toml @@ -8,7 +8,7 @@ version.workspace = true license.workspace = true [target.'cfg(target_os = "macos")'.dependencies] -block = "0.1.6" +block2.workspace = true core-graphics.workspace = true core-foundation.workspace = true cocoa.workspace = true @@ -42,6 +42,3 @@ appkit-nsworkspace-bindings.workspace = true tracing.workspace = true tokio.workspace = true nix.workspace = true - -[dependencies] -block2 = "0.5.1" diff --git a/crates/macos-utils/src/window_server/mod.rs b/crates/macos-utils/src/window_server/mod.rs index 2c1fb76be..2ec3ec336 100644 --- a/crates/macos-utils/src/window_server/mod.rs +++ b/crates/macos-utils/src/window_server/mod.rs @@ -31,6 +31,7 @@ use core_foundation::string::{ }; use dashmap::DashMap; use flume::Sender; +use objc2::mutability::InteriorMutable; use objc2::rc::{ Allocated, Retained, @@ -41,7 +42,6 @@ use objc2::{ DeclaredClass, declare_class, msg_send_id, - mutability, sel, }; use objc2_app_kit::{ @@ -213,9 +213,12 @@ pub struct Ivars { declare_class! { pub struct ObserverClass; + // - The superclass NSObject does not have any subclassing requirements. + // - Interior mutability is a safe default. + // - `ObserverClass` does not implement `Drop`. unsafe impl ClassType for ObserverClass { type Super = NSObject; - type Mutability = mutability::InteriorMutable; + type Mutability = InteriorMutable; const NAME: &'static str = OBSERVER_CLASS_NAME; } From 414855fb07342745615b9f3ffe4a8e82b0162053 Mon Sep 17 00:00:00 2001 From: Grant Gurvis Date: Tue, 31 Dec 2024 17:16:37 -0500 Subject: [PATCH 4/4] fix fig_input_method --- crates/fig_input_method/src/main.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/fig_input_method/src/main.rs b/crates/fig_input_method/src/main.rs index bfa9c5b96..ec7008a61 100644 --- a/crates/fig_input_method/src/main.rs +++ b/crates/fig_input_method/src/main.rs @@ -3,11 +3,14 @@ mod imk; #[cfg(target_os = "macos")] mod macos; +#[cfg(not(target_os = "macos"))] +use std::process::ExitCode; + #[cfg(target_os = "macos")] pub use macos::main; #[cfg(not(target_os = "macos"))] -fn main() -> std::process::ExitCode { +fn main() -> ExitCode { println!("Fig input method is only supported on macOS"); - std::process::ExitCode + ExitCode::FAILURE }