1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-11 14:25:57 +03:00

macos: allow association with .command file type

Implement an app delegate to receive a callback when the system
requests that we open `.command` files, and then ask the mux
layer to spawn a window for it and execute it via the shell.

Also allow handling `.sh`, `.zsh`, `.bash`, `.fish` and `.tool`,
per kitty.

refs: https://github.com/wez/wezterm/issues/2741
refs: https://github.com/wez/wezterm/issues/2871
This commit is contained in:
Wez Furlong 2022-12-19 11:06:12 -07:00
parent e70f97903b
commit 28c7c0ba19
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
7 changed files with 190 additions and 0 deletions

View File

@ -60,5 +60,43 @@
<string>An application launched via WezTerm would like to access your Downloads folder.</string>
<key>NSSystemAdministrationUsageDescription</key>
<string>An application launched via WezTerm requires elevated permission.</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Terminal shell script</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeIconFile</key>
<string>terminal.icns</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>command</string>
<string>sh</string>
<string>zsh</string>
<string>bash</string>
<string>fish</string>
<string>tool</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Folders</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>fold</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Shell</string>
<key>LSItemContentTypes</key>
<array>
<string>public.unix-executable</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@ -15,6 +15,10 @@ As features stabilize some brief notes about them will accumulate here.
* Copy Mode now supports using `CTRL-u` and `CTRL-d` to move by half a page at
a time. Thanks to [@pengux](https://github.com/pengux)!
[#2662](https://github.com/wez/wezterm/pull/2662)
* macOS: allow association with `.command`, `.sh`, `.zsh`, `.bash`, `.fish` and
`.tool` scripts, so that those can open and show their output in the
terminal. [#2871](https://github.com/wez/wezterm/issues/2871)
[#2741](https://github.com/wez/wezterm/issues/2741)
#### Changed
* Window title reporting escape sequences are now disabled by default.

View File

@ -32,6 +32,7 @@ impl Drop for GuiFrontEnd {
impl GuiFrontEnd {
pub fn try_new() -> anyhow::Result<Rc<GuiFrontEnd>> {
let connection = Connection::init()?;
connection.set_event_handler(Self::app_event_handler);
let mux = Mux::get().expect("mux started and running on main thread");
let client_id = mux.active_identity().expect("to have set my own id");
@ -43,6 +44,7 @@ impl GuiFrontEnd {
known_windows: RefCell::new(BTreeMap::new()),
client_id: client_id.clone(),
});
let fe = Rc::downgrade(&front_end);
mux.subscribe(move |n| {
if let Some(fe) = fe.upgrade() {
@ -152,6 +154,53 @@ impl GuiFrontEnd {
Ok(front_end)
}
fn app_event_handler(event: ApplicationEvent) {
log::trace!("Got app event {event:?}");
match event {
ApplicationEvent::OpenCommandScript(file_name) => {
promise::spawn::spawn(async move {
use config::keyassignment::SpawnTabDomain;
use portable_pty::CommandBuilder;
use wezterm_term::TerminalSize;
let cmd = CommandBuilder::from_argv(
["/bin/sh", "-c", &file_name]
.iter()
.map(Into::into)
.collect(),
);
let mux = Mux::get().expect("mux started");
let window_id = None;
let pane_id = None;
let cwd = None;
let workspace = mux.active_workspace();
match mux
.spawn_tab_or_window(
window_id,
SpawnTabDomain::DomainName("local".to_string()),
Some(cmd),
cwd,
TerminalSize::default(),
pane_id,
workspace,
)
.await
{
Ok((_tab, pane, _window_id)) => {
log::trace!("Spawned {file_name} as pane_id {}", pane.pane_id());
}
Err(err) => {
log::error!("Failed to spawn {file_name}: {err:#?}");
}
};
})
.detach();
}
}
}
pub fn run_forever(&self) -> anyhow::Result<()> {
self.connection
.run_message_loop()

View File

@ -4,15 +4,26 @@ use anyhow::Result as Fallible;
use config::DimensionContext;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Mutex;
thread_local! {
static CONN: RefCell<Option<Rc<Connection>>> = RefCell::new(None);
}
fn nop_event_handler(_event: ApplicationEvent) {}
static EVENT_HANDLER: Mutex<fn(ApplicationEvent)> = Mutex::new(nop_event_handler);
pub fn shutdown() {
CONN.with(|m| drop(m.borrow_mut().take()));
}
#[derive(Debug)]
pub enum ApplicationEvent {
/// The system wants to open a command in the terminal
OpenCommandScript(String),
}
pub trait ConnectionOps {
fn get() -> Option<Rc<Connection>> {
let mut res = None;
@ -24,6 +35,16 @@ pub trait ConnectionOps {
res
}
fn set_event_handler(&self, func: fn(ApplicationEvent)) {
let mut handler = EVENT_HANDLER.lock().unwrap();
*handler = func;
}
fn dispatch_app_event(&self, event: ApplicationEvent) {
let func = EVENT_HANDLER.lock().unwrap();
func(event);
}
fn default_dpi(&self) -> f64 {
crate::DEFAULT_DPI
}

View File

@ -0,0 +1,72 @@
use crate::connection::ConnectionOps;
use crate::macos::nsstring_to_str;
use crate::{ApplicationEvent, Connection};
use cocoa::appkit::NSApplicationTerminateReply;
use objc::declare::ClassDecl;
use objc::rc::StrongPtr;
use objc::runtime::{Class, Object, Sel};
use objc::*;
const CLS_NAME: &str = "WezTermAppDelegate";
extern "C" fn application_should_terminate(
_self: &mut Object,
_sel: Sel,
_app: *mut Object,
) -> u64 {
log::debug!("application termination requested");
NSApplicationTerminateReply::NSTerminateLater as u64
}
extern "C" fn application_will_finish_launching(
_self: &mut Object,
_sel: Sel,
_notif: *mut Object,
) {
log::debug!("application_will_finish_launching");
}
extern "C" fn application_open_file(
_self: &mut Object,
_sel: Sel,
_app: *mut Object,
file_name: *mut Object,
) {
let file_name = unsafe { nsstring_to_str(file_name) }.to_string();
if let Some(conn) = Connection::get() {
conn.dispatch_app_event(ApplicationEvent::OpenCommandScript(file_name));
}
}
fn get_class() -> &'static Class {
Class::get(CLS_NAME).unwrap_or_else(|| {
let mut cls = ClassDecl::new(CLS_NAME, class!(NSWindow))
.expect("Unable to register application class");
unsafe {
cls.add_method(
sel!(applicationShouldTerminate:),
application_should_terminate as extern "C" fn(&mut Object, Sel, *mut Object) -> u64,
);
cls.add_method(
sel!(applicationWillFinishLaunching:),
application_will_finish_launching as extern "C" fn(&mut Object, Sel, *mut Object),
);
cls.add_method(
sel!(application:openFile:),
application_open_file as extern "C" fn(&mut Object, Sel, *mut Object, *mut Object),
);
}
cls.register()
})
}
pub fn create_app_delegate() -> StrongPtr {
let cls = get_class();
unsafe {
let delegate: *mut Object = msg_send![cls, alloc];
let delegate: *mut Object = msg_send![delegate, init];
StrongPtr::new(delegate)
}
}

View File

@ -4,6 +4,7 @@
use super::nsstring_to_str;
use super::window::WindowInner;
use crate::connection::ConnectionOps;
use crate::os::macos::app::create_app_delegate;
use crate::screen::{ScreenInfo, Screens};
use crate::spawn::*;
use crate::Appearance;
@ -33,6 +34,10 @@ impl Connection {
unsafe {
let ns_app = NSApp();
ns_app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
let delegate = create_app_delegate();
let () = msg_send![ns_app, setDelegate: delegate];
let conn = Self {
ns_app,
windows: RefCell::new(HashMap::new()),

View File

@ -4,6 +4,7 @@ use objc::rc::StrongPtr;
use objc::runtime::Object;
use objc::*;
mod app;
pub mod bitmap;
pub mod connection;
pub mod window;