feat: add ability to open files from Finder in macOS (#2395)

* non-working plist attempt

* add Neovide.icns

* remove old build attempt

* make-icns script

* add background for dmg installer

* app + dmg builder script

* first version of Info.plist

* add missing keys from production build

* support retina backgrounds with tiff

* add lots of types...

* also add exported type declarations

* consistent quotes

* sigh

* comment

* feat(mac): implement openFiles on macOS

* feat: default to fork if in tty

* add source image and instructions

---------

Co-authored-by: Travis <travis@marketcake.com>
This commit is contained in:
Alexander Polakov 2024-04-06 19:31:39 +04:00 committed by GitHub
parent 810fb9ac01
commit fcf5e87bf8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 2606 additions and 11 deletions

View file

@ -131,4 +131,3 @@ long_description = """
This is a simple graphical user interface for Neovim. Where possible there are some graphical improvements, but it should act functionally like the terminal UI.
"""
osx_minimum_system_version = "10.11"

View file

@ -0,0 +1,21 @@
### OSX DMG Background
source.pdf in this directory can be used for modifying the background of the
dmg installer on OSX.
JetBrainsMono can be downloaded from [Nerd Fonts](https://www.nerdfonts.com/font-downloads).
After any changes, you must export 2 images with the following paths, filenames
and dimensions:
1. `assets/neovide-dmg-background.png` (650x450)
2. `assets/neovide-dmg-background@2x.png` (1300x900)
Next, to support retina images you must generate a properly composed tiff by running the builder script from the root of the project:
```
macos-builder/make-icns
```
This script will also generate an `.icns` file with all the proper sizes from
the `assets/neovide-1024.png` source image.

Binary file not shown.

BIN
assets/neovide-1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

File diff suppressed because it is too large Load diff

Binary file not shown.

32
macos-builder/make-icns Executable file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -e
ASSETS_DIR="assets"
ICONSET_DIR="extra/osx/Neovide.app/Contents/Resources/Neovide.iconset"
mkdir -p "${ICONSET_DIR}"
# make tiff (for dmg background to support retina)
tiffutil \
-cathidpicheck \
"${ASSETS_DIR}/neovide-dmg-background.png" \
"${ASSETS_DIR}/neovide-dmg-background@2x.png" \
-out "${ASSETS_DIR}/neovide-dmg-background.tiff"
# make icns
sips -z 16 16 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_16x16.png"
sips -z 32 32 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_16x16@2x.png"
sips -z 32 32 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_32x32.png"
sips -z 64 64 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_32x32@2x.png"
sips -z 128 128 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_128x128.png"
sips -z 256 256 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_128x128@2x.png"
sips -z 256 256 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_256x256.png"
sips -z 512 512 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_256x256@2x.png"
sips -z 512 512 "${ASSETS_DIR}/neovide-1024.png" --out "${ICONSET_DIR}/icon_512x512.png"
cp "${ASSETS_DIR}/neovide-1024.png" "${ICONSET_DIR}/icon_512x512@2x.png"
iconutil -c icns "${ICONSET_DIR}"
rm -R "${ICONSET_DIR}"

49
macos-builder/run Executable file
View file

@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -e
TARGET="neovide"
EXTRAS_DIR="extra"
ASSETS_DIR="assets"
RELEASE_DIR="target/release"
BUNDLE_DIR="${RELEASE_DIR}/bundle"
APP_NAME="Neovide.app"
APP_TEMPLATE="${EXTRAS_DIR}/osx/${APP_NAME}"
APP_DIR="${BUNDLE_DIR}/osx"
APP_BINARY="${RELEASE_DIR}/${TARGET}"
APP_BINARY_DIR="${APP_DIR}/${APP_NAME}/Contents/MacOS"
APP_EXTRAS_DIR="${APP_DIR}/${APP_NAME}/Contents/Resources"
DMG_NAME="Neovide.dmg"
DMG_VOLNAME="Neovide"
DMG_FILESYSTEM="HFS+"
DMG_FORMAT="UDZO"
DMG_ICNS="${APP_EXTRAS_DIR}/Neovide.icns"
DMG_BACKGROUND="${ASSETS_DIR}/neovide-dmg-background.tiff"
# Make Neovide.app
mkdir -p "${APP_BINARY_DIR}"
mkdir -p "${APP_EXTRAS_DIR}"
cp -fRp "${APP_TEMPLATE}" "${APP_DIR}"
cp -fp "${APP_BINARY}" "${APP_BINARY_DIR}"
touch -r "${APP_BINARY}" "${APP_DIR}/${APP_NAME}"
codesign --remove-signature "${APP_DIR}/${APP_NAME}"
codesign --force --deep --sign - "${APP_DIR}/${APP_NAME}"
echo "Created '${APP_NAME}' in '${APP_DIR}'"
# Make Neovide.dmg
create-dmg \
--filesystem "${DMG_FILESYSTEM}" \
--format "${DMG_FORMAT}" \
--volname "${DMG_VOLNAME}" \
--volicon "${DMG_ICNS}" \
--background "${DMG_BACKGROUND}" \
--window-size 650 470 \
--icon-size 80 \
--icon Neovide.app 240 320 \
--app-drop-link 410 320 \
"${APP_DIR}/${DMG_NAME}" \
"${APP_DIR}/${APP_NAME}"

View file

@ -1,7 +1,6 @@
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use std::io::IsTerminal;
use std::process::{Command as StdCommand, Stdio};
use anyhow::{bail, Result};
@ -233,10 +232,6 @@ fn platform_which(bin: &str) -> Option<String> {
None
}
pub fn is_tty() -> bool {
std::io::stdout().is_terminal()
}
#[cfg(target_os = "macos")]
fn nvim_cmd_impl(bin: String, args: Vec<String>) -> TokioCommand {
let (cmd, cmd_args) = build_login_cmd_args(

View file

@ -78,7 +78,7 @@ fn handle_command_arg_as_path_or_default(args: &mut Vec<String>) -> Option<Strin
pub async fn setup_tty_startup_directory(
nvim: &Neovim<NeovimWriter>,
) -> Result<(), Box<CallError>> {
use self::command::is_tty;
use crate::utils::is_tty;
if is_tty() {
return Ok(());

View file

@ -9,7 +9,8 @@ use crate::{
settings::{SettingLocation, SETTINGS},
};
use crate::bridge::{command::is_tty, setup_tty_startup_directory};
use crate::bridge::setup_tty_startup_directory;
use crate::utils::is_tty;
const INIT_LUA: &str = include_str!("../../lua/init.lua");

View file

@ -1,5 +1,6 @@
use std::{iter, mem};
use crate::utils::is_tty;
use crate::{dimensions::Dimensions, frame::Frame, settings::*};
use anyhow::Result;
@ -21,6 +22,10 @@ fn get_styles() -> Styles {
.placeholder(styling::AnsiColor::Cyan.on_default())
}
fn is_tty_str() -> &'static str {
is_tty().then_some("1").unwrap_or("0")
}
#[derive(Clone, Debug, Parser)]
#[command(version, about, long_about = None, styles = get_styles())]
pub struct CmdLineSettings {
@ -66,7 +71,7 @@ pub struct CmdLineSettings {
pub title_hidden: bool,
/// Spawn a child process and leak it [DEFAULT]
#[arg(long = "fork", env = "NEOVIDE_FORK", action = ArgAction::SetTrue, default_value = "1", value_parser = FalseyValueParser::new())]
#[arg(long = "fork", env = "NEOVIDE_FORK", action = ArgAction::SetTrue, default_value = is_tty_str(), value_parser = FalseyValueParser::new())]
pub fork: bool,
/// Be "blocking" and let the shell persist as parent process. Takes precedence over `--fork`.

View file

@ -1,3 +1,8 @@
mod ring_buffer;
pub use ring_buffer::*;
pub fn is_tty() -> bool {
use std::io::IsTerminal;
std::io::stdout().is_terminal()
}

View file

@ -7,7 +7,13 @@ use icrate::{
},
Foundation::{MainThreadMarker, NSObject, NSPoint, NSProcessInfo, NSRect, NSSize, NSString},
};
use objc2::{declare_class, msg_send_id, mutability::InteriorMutable, rc::Id, sel, ClassType};
use objc2::{
declare_class, msg_send_id,
mutability::InteriorMutable,
rc::Id,
runtime::{AnyClass, AnyObject},
sel, ClassType,
};
use csscolorparser::Color;
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
@ -409,3 +415,47 @@ impl Menu {
}
}
}
pub fn register_file_handler() {
use objc2::rc::autoreleasepool;
extern "C" fn handle_open_files(
_this: &mut AnyObject,
_sel: objc2::runtime::Sel,
_sender: &objc2::runtime::AnyObject,
files: &mut icrate::Foundation::NSArray<icrate::Foundation::NSString>,
) {
autoreleasepool(|pool| {
for file in files.iter() {
let path = file.as_str(pool).to_owned();
send_ui(ParallelCommand::FileDrop(path));
}
});
}
unsafe {
use objc2::declare::ClassBuilder;
use objc2::msg_send;
let app = NSApplication::sharedApplication();
let delegate = app.delegate().unwrap();
// Find out class of the NSApplicationDelegate
let class: &AnyClass = msg_send![&delegate, class];
// register subclass of whatever was in delegate
let mut my_class = ClassBuilder::new("NeovideApplicationDelegate", class).unwrap();
my_class.add_method(
sel!(application:openFiles:),
handle_open_files as unsafe extern "C" fn(_, _, _, _) -> _,
);
let class = my_class.register();
// this should be safe as:
// * our class is a subclass
// * no new ivars
// * overriden methods are compatible with old (we implement protocol method)
let delegate_obj = Id::cast::<AnyObject>(delegate);
AnyObject::set_class(&delegate_obj, class);
}
}

View file

@ -120,7 +120,10 @@ pub fn create_event_loop() -> EventLoop<UserEvent> {
let mut builder = EventLoopBuilder::<UserEvent>::with_user_event();
#[cfg(target_os = "macos")]
builder.with_default_menu(false);
builder.build().expect("Failed to create winit event loop")
let event_loop = builder.build().expect("Failed to create winit event loop");
#[cfg(target_os = "macos")]
crate::window::macos::register_file_handler();
event_loop
}
pub fn create_window(