From c6d503b90a8c9793e15607798958d2ce4f2c4899 Mon Sep 17 00:00:00 2001 From: wansho Date: Fri, 17 Apr 2026 14:54:01 +0800 Subject: [PATCH 1/2] feat: add Ghostty terminal support --- README.md | 43 +++++++++++++ Shuttle.xcodeproj/project.pbxproj | 12 ++++ Shuttle/AppDelegate.m | 22 ++++++- Shuttle/Shuttle.entitlements | 1 + .../apple-scpt/ghostty-current-window.scpt | Bin 0 -> 4630 bytes .../apple-scpt/ghostty-new-tab-default.scpt | Bin 0 -> 4958 bytes Shuttle/apple-scpt/ghostty-new-window.scpt | Bin 0 -> 3838 bytes Shuttle/shuttle.default.json | 4 +- .../ghostty-current-window.applescript | 28 +++++++++ .../ghostty-new-tab-default.applescript | 29 +++++++++ .../ghostty/ghostty-new-window.applescript | 23 +++++++ tests/test_ghostty_support.py | 59 ++++++++++++++++++ 12 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 Shuttle/apple-scpt/ghostty-current-window.scpt create mode 100644 Shuttle/apple-scpt/ghostty-new-tab-default.scpt create mode 100644 Shuttle/apple-scpt/ghostty-new-window.scpt create mode 100644 apple-scripts/ghostty/ghostty-current-window.applescript create mode 100644 apple-scripts/ghostty/ghostty-new-tab-default.applescript create mode 100644 apple-scripts/ghostty/ghostty-new-window.applescript create mode 100644 tests/test_ghostty_support.py diff --git a/README.md b/README.md index 47daeaa..44e52b4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,49 @@ A simple shortcut menu for macOS 1. Download [Shuttle](http://fitztrev.github.io/shuttle/) 2. Copy to Applications +## Ghostty support + +This fork adds support for using [Ghostty](https://ghostty.org/) as the terminal launched by Shuttle. + +### What changed + +* Added `Ghostty.app` as a valid value for the top-level `terminal` setting. +* Added bundled AppleScript handlers for Ghostty: + * new window + * current window + * new tab +* Added the Ghostty Apple Events entitlement, `com.mitchellh.ghostty`. +* Fixed command launching on newer macOS versions by only treating strings with a URL scheme as URLs. Shell commands such as `ssh user@host` and custom commands such as `ssh-server ...` now continue through the terminal launcher instead of being passed to Finder as invalid URLs. + +### Configuration + +Set `terminal` to `Ghostty.app` in `~/.shuttle.json`: + +```json +{ + "terminal": "Ghostty.app", + "open_in": "new" +} +``` + +`open_in` continues to support Shuttle's existing values: + +* `new` opens a new Ghostty window. +* `tab` opens a new tab in the front Ghostty window, or creates a window if none exists. +* `current` runs the command in the focused terminal of the front Ghostty window, or creates a window if none exists. + +Per-host `inTerminal` overrides still work the same way: + +```json +{ + "name": "Example server", + "cmd": "ssh user@example.com", + "inTerminal": "tab" +} +``` + +Ghostty support requires Ghostty 1.3.0 or newer because it relies on Ghostty's macOS AppleScript API. The first time Shuttle controls Ghostty, macOS may ask for Automation permission; allow Shuttle to control Ghostty. + ## Help See the [Wiki](https://github.com/fitztrev/shuttle/wiki) pages. diff --git a/Shuttle.xcodeproj/project.pbxproj b/Shuttle.xcodeproj/project.pbxproj index d174fef..b9711d6 100644 --- a/Shuttle.xcodeproj/project.pbxproj +++ b/Shuttle.xcodeproj/project.pbxproj @@ -18,6 +18,9 @@ A124BA191D45572B00218F2F /* iTerm2-stable-current-window.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A124BA161D45572B00218F2F /* iTerm2-stable-current-window.scpt */; }; A124BA1A1D45572B00218F2F /* iTerm2-stable-new-tab-default.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A124BA171D45572B00218F2F /* iTerm2-stable-new-tab-default.scpt */; }; A124BA1B1D45572B00218F2F /* iTerm2-stable-new-window.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A124BA181D45572B00218F2F /* iTerm2-stable-new-window.scpt */; }; + A124BA1C1D45572B00218F2F /* ghostty-current-window.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A124BA1F1D45572B00218F2F /* ghostty-current-window.scpt */; }; + A124BA1D1D45572B00218F2F /* ghostty-new-tab-default.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A124BA201D45572B00218F2F /* ghostty-new-tab-default.scpt */; }; + A124BA1E1D45572B00218F2F /* ghostty-new-window.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A124BA211D45572B00218F2F /* ghostty-new-window.scpt */; }; A12D9BF31BCF2C73004F52A6 /* iTerm2-nightly-current-window.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A12D9BEA1BCF2C73004F52A6 /* iTerm2-nightly-current-window.scpt */; }; A12D9BF41BCF2C73004F52A6 /* iTerm2-nightly-new-tab-default.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A12D9BEB1BCF2C73004F52A6 /* iTerm2-nightly-new-tab-default.scpt */; }; A12D9BF51BCF2C73004F52A6 /* iTerm2-nightly-new-window.scpt in Resources */ = {isa = PBXBuildFile; fileRef = A12D9BEC1BCF2C73004F52A6 /* iTerm2-nightly-new-window.scpt */; }; @@ -48,6 +51,9 @@ A124BA161D45572B00218F2F /* iTerm2-stable-current-window.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iTerm2-stable-current-window.scpt"; sourceTree = ""; }; A124BA171D45572B00218F2F /* iTerm2-stable-new-tab-default.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iTerm2-stable-new-tab-default.scpt"; sourceTree = ""; }; A124BA181D45572B00218F2F /* iTerm2-stable-new-window.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iTerm2-stable-new-window.scpt"; sourceTree = ""; }; + A124BA1F1D45572B00218F2F /* ghostty-current-window.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty-current-window.scpt"; sourceTree = ""; }; + A124BA201D45572B00218F2F /* ghostty-new-tab-default.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty-new-tab-default.scpt"; sourceTree = ""; }; + A124BA211D45572B00218F2F /* ghostty-new-window.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty-new-window.scpt"; sourceTree = ""; }; A12D9BEA1BCF2C73004F52A6 /* iTerm2-nightly-current-window.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iTerm2-nightly-current-window.scpt"; sourceTree = ""; }; A12D9BEB1BCF2C73004F52A6 /* iTerm2-nightly-new-tab-default.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iTerm2-nightly-new-tab-default.scpt"; sourceTree = ""; }; A12D9BEC1BCF2C73004F52A6 /* iTerm2-nightly-new-window.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iTerm2-nightly-new-window.scpt"; sourceTree = ""; }; @@ -120,6 +126,9 @@ A124BA161D45572B00218F2F /* iTerm2-stable-current-window.scpt */, A124BA171D45572B00218F2F /* iTerm2-stable-new-tab-default.scpt */, A124BA181D45572B00218F2F /* iTerm2-stable-new-window.scpt */, + A124BA1F1D45572B00218F2F /* ghostty-current-window.scpt */, + A124BA201D45572B00218F2F /* ghostty-new-tab-default.scpt */, + A124BA211D45572B00218F2F /* ghostty-new-window.scpt */, A12D9BEA1BCF2C73004F52A6 /* iTerm2-nightly-current-window.scpt */, A12D9BEB1BCF2C73004F52A6 /* iTerm2-nightly-new-tab-default.scpt */, A12D9BEC1BCF2C73004F52A6 /* iTerm2-nightly-new-window.scpt */, @@ -256,6 +265,7 @@ buildActionMask = 2147483647; files = ( A12D9BF61BCF2C73004F52A6 /* terminal-current-window.scpt in Resources */, + A124BA1C1D45572B00218F2F /* ghostty-current-window.scpt in Resources */, A12D9BF31BCF2C73004F52A6 /* iTerm2-nightly-current-window.scpt in Resources */, A1B7B9DD1DB53ED200809327 /* Localizable.strings in Resources */, A12D9BF51BCF2C73004F52A6 /* iTerm2-nightly-new-window.scpt in Resources */, @@ -267,12 +277,14 @@ 0ADB3B0F178EF8DB004E9BB9 /* StatusIconAlt@2x.png in Resources */, 0ADB3B0E178EF8DB004E9BB9 /* StatusIcon@2x.png in Resources */, A124BA191D45572B00218F2F /* iTerm2-stable-current-window.scpt in Resources */, + A124BA1D1D45572B00218F2F /* ghostty-new-tab-default.scpt in Resources */, A12D9BF41BCF2C73004F52A6 /* iTerm2-nightly-new-tab-default.scpt in Resources */, C149EC0E15D5214600B1F558 /* Credits.rtf in Resources */, A12D9BF71BCF2C73004F52A6 /* terminal-new-tab-default.scpt in Resources */, C149EC1415D5214600B1F558 /* MainMenu.xib in Resources */, A124BA1B1D45572B00218F2F /* iTerm2-stable-new-window.scpt in Resources */, A124BA1A1D45572B00218F2F /* iTerm2-stable-new-tab-default.scpt in Resources */, + A124BA1E1D45572B00218F2F /* ghostty-new-window.scpt in Resources */, C159DC2815D5DE8000F5DE24 /* shuttle.icns in Resources */, A12D9BF81BCF2C73004F52A6 /* terminal-new-window.scpt in Resources */, 7E74A7C61789CE2F0079E0D2 /* shuttle.default.json in Resources */, diff --git a/Shuttle/AppDelegate.m b/Shuttle/AppDelegate.m index 2f77935..0733601 100644 --- a/Shuttle/AppDelegate.m +++ b/Shuttle/AppDelegate.m @@ -546,6 +546,11 @@ - (void) openHost:(NSMenuItem *) sender { NSString *terminalNewWindow = [[NSBundle mainBundle] pathForResource:@"terminal-new-window" ofType:@"scpt"]; NSString *terminalCurrentWindow = [[NSBundle mainBundle] pathForResource:@"terminal-current-window" ofType:@"scpt"]; NSString *terminalNewTabDefault = [[NSBundle mainBundle] pathForResource:@"terminal-new-tab-default" ofType:@"scpt"]; + + //Set Paths to Ghostty AppleScripts + NSString *ghosttyNewWindow = [[NSBundle mainBundle] pathForResource:@"ghostty-new-window" ofType:@"scpt"]; + NSString *ghosttyCurrentWindow = [[NSBundle mainBundle] pathForResource:@"ghostty-current-window" ofType:@"scpt"]; + NSString *ghosttyNewTabDefault = [[NSBundle mainBundle] pathForResource:@"ghostty-new-tab-default" ofType:@"scpt"]; //Set Path to virtual with screen AppleScripts NSString *terminalVirtualWithScreen = [[NSBundle mainBundle] pathForResource:@"virtual-with-screen" ofType:@"scpt"]; @@ -564,11 +569,26 @@ - (void) openHost:(NSMenuItem *) sender { passParameters = @[escapedObject, terminalTitle]; } // Check if Url - if (url) + if (url && [url scheme]) { [[NSWorkspace sharedWorkspace] openURL:url]; } + //If the JSON file is set to use Ghostty + else if ( [terminalPref rangeOfString: @"ghostty"].location !=NSNotFound ) { + if ( [terminalWindow isEqualToString:@"new"] ) { + [self runScript:ghosttyNewWindow handler:handlerName parameters:passParameters]; + } + if ( [terminalWindow isEqualToString:@"current"] ) { + [self runScript:ghosttyCurrentWindow handler:handlerName parameters:passParameters]; + } + if ( [terminalWindow isEqualToString:@"tab"] ) { + [self runScript:ghosttyNewTabDefault handler:handlerName parameters:passParameters]; + } + if ( [terminalWindow isEqualToString:@"virtual"] ) { + [self runScript:terminalVirtualWithScreen handler:handlerName parameters:passParameters]; + } + } //If the JSON file is set to use iTerm else if ( [terminalPref rangeOfString: @"iterm"].location !=NSNotFound ) { diff --git a/Shuttle/Shuttle.entitlements b/Shuttle/Shuttle.entitlements index 4a11143..7e9a5cc 100644 --- a/Shuttle/Shuttle.entitlements +++ b/Shuttle/Shuttle.entitlements @@ -6,6 +6,7 @@ com.apple.terminal com.googlecode.iterm2 + com.mitchellh.ghostty diff --git a/Shuttle/apple-scpt/ghostty-current-window.scpt b/Shuttle/apple-scpt/ghostty-current-window.scpt new file mode 100644 index 0000000000000000000000000000000000000000..03a9b26d341455d42311a02fc9c940de72f5cc7d GIT binary patch literal 4630 zcma)931C#!6+QnaCh$Km$zVmQpkpcsAwYU$YKo@|YFCS$2Y!b|#5zs-x5r;^chXR~a=GZ8r4hOCwERA8kyx zW@-}6Q9l(e@5n?OJLg7EingY`j_AzHtV~;ZsHndx+CGBqaWJ=@;Z}50jod~nM>X7N zGsiwj=oUNpPG>tYm+eV>S}E!AeH1e}H;Ydj%59W!^`Bq|L&(wm4g;{GCEqE{0bFcQ zj%IzEm+%6jlT!(=DeZN2v)fVG4UAwQ_UeIMJ#~4(QREp;CR06}rhr$Ma@m1geu|w8 zA;;xg?2JK{e5+h`#!y7_T;hIv$fc$;`sYc7;Xq$j6ujC{z|@-FD&^A zu;fdHFTfeyTm?oLf*n^m6rd6#4WVF4FoygGqvUg!&44O&8%;^%?PoJ5*feRCAy}#I z&pvbIGxV=Qv068lXbwA!8yi&=AA@Q`$ccaI%BO(_6=y0*c<8I$;5ckl^OHU06N5eT zpM31f$N5k6thztZt}UN%gYvnixRKcu=U}Xjv0Uos7-tA}Iv`tsUtljoptZK+8w=y* zBl*x^d{&tyAGq>CkK3{2BQ@uTs%WpQJmh^>-Vfxv;(lAOeeDF>A4dXv%Xv4!-$F2?D?Sx-pnjzS_pJE149S$`FI~mM7_%#kQ1Zxb(eRv4}=6@7A9W=aQ zSThCd+IBG=zZAV3f;q@~k}L z$a5rA+m`XCO|NN5cDA>R%!5AKx8zwpmuFPL%=|e%?aI^Gr3xm~nQm!}d9hToCEl7F z)&xTvN0P+Rm}Lk7i6KlD$KY5)2uKV;r9351;y6d1%93b^CpRbYBzV~$hXVO08s!Ox z4ec_mYr0{Y4YJ4{7$%Rq^7vLs70DC2mt*sLdCZl^FsKUse5M@F?}{gzQ(f5+*>Gg@ z3bxTCj~d>9!A?fg$}uz>Lf}!OnOQuv7((Dt)fg*}IP!?{tZ$)bv}EO~=~-=t5Xe=b zS{`=fVU=shP_8W`#)K*lx$@9fh#6-7To!%Ml?M^6LP@w0-%BUr$;S5VxW0RA%cFXv z>(va0gg!H#F%-9XrP-K`Iff8yay*kkI}(NvkR8ti^nfD|DA`F343k7&`TefkzZJ4J zv?xr6UQ4u;L)|1CjUSe<~nS`u$q#r zpPTh)jr5p5m(06DjkTC8ce-+Ck4dqJ^$Jh{zbL#|Uja-A1)WwMo9UAc9exZb8*Pvmo&++uKA$h84S%U^K1-0ZLk zxlW38W`4{y)igV5aC)e*0Y}I>SJrKVMq6&yfVf2iViN4JdPpdgn{b9hD0E_t9dyw% zLL2b66Xus-zS>|x1Pf5jy5~STPzR$V&UDZil%x8qCBvCo26QSYRehe@D9%#SW}Rze zp{zAnn7@v1lpExFOK#L0e}lsH%4)6tieeR4T1Lf%avc_7F_u{D@XA<3fotU&SFY`~ zK6B-oE!@&2T&%cE{)~}wwOoazSSDAx9QIc2mB`hw3*}E(UO*@)!E&;aE97!nWS;>D%Q*y!Q&~x61C(XnTalvKVSt`%7re6y_a+)a*^8d z_gGaxuu8Cs_r6@<$^|{Lvbielv4ZwEU(Sq2$Qf>}j3l$XS{wSl>8u zCWE@Ss>qR5lw;M^?#RmS;P0teafH?h>Hr(&# literal 0 HcmV?d00001 diff --git a/Shuttle/apple-scpt/ghostty-new-tab-default.scpt b/Shuttle/apple-scpt/ghostty-new-tab-default.scpt new file mode 100644 index 0000000000000000000000000000000000000000..9487175d759136b5f3010bfc4fe31b79530aa7d6 GIT binary patch literal 4958 zcma)933yc175?WX1b8nu$pi&d)G-x=5FkWqMXjij%_vccqEf4q$s`%b%*2@&6A(f6 zMGz3FyJgY3RI9kuy4Sj|b*sC$@4MDrd-|XEG8u)5kbLjld*40pp8uY6?tjmW3#S{7 zIe2nt)2@KlvYeqE~`0dI!-PN1_o) zq!5CODAJTMB+$fh8`}uy;{1=Y?|M4T?1xas_X)NsPpgc*dbIFe1k)`P(1s1iP;}w_ zi8xI_ep0;pU*LoC(Qw0?P!uGt!k@2e!Qp%{N(@C_X6HX5h|+-ks4E3A6a#ywqi!gi zYD!nf8$xa}RMwgfh1)tpvqDX&XlrO{`owf|nQy364fP444-Vtj)7*+~D$H#(aa7HX zHgN0`gl@5w?{u~e9c*XuX`*J7??af%xfA&`qs&4HSKk3U8j3EMKcFxA1>^^X*_VqA z=z>|-9F0dkpc9huXniW$-ifZCqU#?-f9#tBT^(&jy-;KkP9RbZPQ8b#U9s#)EIVOm zL(zrhd+dS%0r_6B?1DiEWwFHE79UG>TiQ*=L$1E)#4|wg>>9+bI4CEc7(LZO>sdV2 z^p&QNqSCh+4dU9FxOT(thN27CcU0OVAm1sj5Jq4~?{p*;YjuxqOAPJ|XoVM9&t;um z1vtbH=N<~MEJ*Jj?E#F?4Kaqta(fMzhH~A{O?f#$XIF6kLplkcapi`C7gT z$TvVhzE<%SIHQ{@$52CI#Z(V@sK78okvGvBL;j25@}GYscIEJ_N9zd|g@;>&L_Z&9;D`6|6h7%3yWxLw%Gfge*&kqlfcMT5k?U1PD9Y@~DZAac!2fU}^ z1C8}iKEKC-hN3Gg-j=uI&49eEJ9taQo2q#rpD{SdP?G6BM*U9>xCwcu^4A4yi2MlIbghn@mzKSCK?Ja zf<1Wz4nvKhAU=y|3uUvsA}`yr8U2TkZ%(_DCRaBm+FDvf&VWAJ56CNO@MSevlLh#a zBQIgMN|Lx<1u@>`sn}k|<5l0$!tnuEvG8~1Y z4TYsnEhcMEMxCLs65f0%j=`~pA~2HU5|qjdw!DCRyOq&5I2ODIbZy>CbqK`SFGU}D z-j?Ul%c)N$PjypmQ9};07*+Hm2TZ|nhQdqbeq_MoyugXJ+t9@}Q@3gmIcc~Umyk2>-w22`TZCFMlj_E@4J z*`65@4M!HQU<>u~h~W+B?PLTu7(s)f@Pavl%o0VTq40uPg;DabEe|Wsx<&@QF{4+- zpf(!{Pp=YH@{lbLsos<{^_qQPBvg6Okq5T{%#iuJSo8r$9zduPMgB(IXeu5{gj+J> zy3VmBk7(wfp)Op%bw1pq`H*5h{7cIK zn~mm!JH>G5`GbrlcROxq>I&$ZBB-LH&%~P`O zxWkb7=U(k7z!_)YDgk8Fw;a=TsxNANifvkZk7oTJDb|G?>(ZL{%- zbz-8fBNH6ukBHmh8mf2}$$fIOBRA*d`vT0S zumy6Hf}O?Z49qnY9@wd9lp8P)^KCXh*vU1~8C!#$=X2OpQqqDVEYP}YVGs-XlfwTg z5=E*2^NIC(ERsz&8)B`A66+!#YnZ%nog>$67uHRRbuOR9a;?E)A8Qj@7Qdw)=vu=Eb%RpKjdmhuHFudmRzG}{aVe=aj-^dkdQBb#ZsFh-;RV^TMd@_ zKH$$nEGxn?^}(4zoQW!ybO-Znwvqs1WV*0dY3mQNG-t+>tCC(7?lMh>bC#MGOLz;* z za*-pKcuBlvG3&_1-g3FBR!1(%&TUnmuGcND;-)W@3*>w`&yfqev2a0ll`ZOGN6ydA zZDGNY^Ku@$60D~8&voS7U-4W}*1=gf_J-Q>jnjuRJVjWkp8OLjb*-F(H8@AsI2?9g ztrp2z_2t=En@15S!dg}nvf7c=IZ;_$mHt?R9-L_w@Js2oV5+! zcVxwOoE}G(dmhfk;Uzc+edSCnlV!40mdIjRBnxGM%$IpGSI&?*GFwiUS#lZ{$V{0b z9X6Hy182*!%;lD&FtueV^|S)CWr>mvD=%9X^Na54HCq-@kM&@SEerJv7Dl!#$k@nH zP}wqH^;mG&GEaNFQ*4>5J>Kg!OJrTeonp%zzc)EgX0s=!XBN$IE88+lmC55acisI3 zVarU_BZt~Dqf;*zH`~(DIh%_OgR%yRA+NnYq7+T#(+XjY-BertW<@k$!<4@@jBk2O z!#jg^Mc?vw*S8d+*VyZ8H@si*LB-e`j>k^rA69T&{?Uey*MCxo;;}c@UaB*->%u76 hNbO*47^PwK(f$s#8@bw!#}tQ2Ld@gB?>BG0=Kp-;TmJw6 literal 0 HcmV?d00001 diff --git a/Shuttle/apple-scpt/ghostty-new-window.scpt b/Shuttle/apple-scpt/ghostty-new-window.scpt new file mode 100644 index 0000000000000000000000000000000000000000..3433f0f19cf655517569f1940490d86981657ced GIT binary patch literal 3838 zcmai0`+ro`5&z!1YeMeL*<`UIV8lxlOh|x4DdMA6iM*r|B%#z&teeeEvLw43cW*wy z&&MD7cc@h$^;L@+P^(t!qgu7qTHp1BAOebStuNG0zjHU6RoH~x&z&>poH=K{Gjq<& zS)RyuufMRvYhBRVqF)R^v_=FLY(y+FFyyU%j_WUKBg&cMP+>?S1DxYe{)b9TvDl!R z+yXT8WOH8N=Yv$H*Bie}_1xTJoLl6h{;0ROS7^OXPa< zZRu_=$a?jIc`q@v#k<<;&H01g#{8yyUwuK*R8@3*9LM8gnx3aAYE*(|^s?7RL%Z1z z2trL9}20ZZFZKFFrMKnKR~)G3~m<2V@?l*N;xrv|9Kgr|+Z(j%m( zbhIczTr-Jl7EUoFV-QCuDtqOmHU%%ui{bwinkv4A#*kquoo#wx(ZLO5qD z!1_47yDkKnq#IHUPr$yO-i9MW+e|IRV3*wanA8gnq$lCKr= z9NyGQKhv$Zs{m-O0y;g8(|MHj%IIkduZWG)DA9mML&D8?33-XXk}u^8OTGdu`BKRj z;EWnK4^4)|PN^IsXvTa)5?K}YA^*h!`P^kQtp#IFQxGNp*~|$xUD{$utnApb&s_No zQ(I7}-c6>v3k>g#jTVZx;tWGFF8--2pN1M#oT()3qe|H@95&A2k~8HKgELD@K6d5f z(h`l;*b-%J`Gf|_=bGZ?7gL;rMK%_3tDj=AA+d8I*a*(T5<^079mf$1XUj+Op~2Zj zWtM#4$_Hi5vE(Cl=ZC6jNl_m1zANvC^24d1FEn47(EP;&aE=@@IH%y`C>(hY=gPY- zo9QiZG@~Xm-Rfnht-$k`+BlaB&y#}&=N0rYQOf~W4wTm;2h{=ZDmkRFZsPYdoNq|R zPsM(DNA_8=UoCh?$v%}lpWn}MfguS`MU-j(LM$~Tsq-kub28#lU0_844L za?D$Jq@$U3nd|T43@+xt>JQ zPi8YcsowE!TiCUsihhAh3`q#0hPiYN+6_qvq6X9CHF*_l9eJ$?VnZr(G>BKhDz+98 z`429YR~$A`XT_S~4ueYzpjd+47PrFNIem;|~v~GTqr>RFNeP zvNkTE4eLNn>k2l^U=HcTdP8Do=>HkapufZhLt?eEUx!)pq9ZRV!p@#fgAGNwI{XTk z8In+LChFt`M_y354SC94R)EA}A^1@PPYkAt0rzb^NAxWbTxFdNY#|73aSa@Z7LcC7ZV zIvQpIR+^X_nV*v}zId4OyW>ptPMGrb6_u-CMUKcn;LB4E8=_q86Qy53*@R|!(v>HV zf$}Luc?G{7dBX6O7NTs$V);9I<#C5i0p;@KQ2vJ~vt64#!>3B+z(OXWLH-9gnxqT0 z@ay|9&zTimmkl2U0j^?EN~omG7ln;}dDNi4l=vQzhvgwl9?_ilu#$%q)uZ}eWxcGa zrQ)dkm0HrsSY-I_VkMOaii(vbMT%ceq`-BI?RrrMaW3<$Y>mmPYTA zoiZwSy0U8m4LeJ_98soS87<8np~00q%U-ft#>kR8T)ATs&&B0lxLcR)YM(dK-^B35 z7;t*>b}W@0@+Urf@^YKY?!?72COg!Ze?$-=v0^+o7UWh}ZY_(-=C1Tdj{dksZkC(m zMpterE9uJ3$IQ#F+!XqyToQKW#!2r>Ezh%&KOiL|vR$^xR=Gj0m)|2P*U7bVjl-$J zrEz4WSZ2!9I!Cr^YG7G$WLr_LOgVC7tI9EHJ92|^O!JOhuN;$~L&XzkRY$H+ zt{syrm)0D)W^A@xI*H3|Skky>@4~5QowvCf_Oj9TdHc2vR3o}MPj{gJtB^bc~ literal 0 HcmV?d00001 diff --git a/Shuttle/shuttle.default.json b/Shuttle/shuttle.default.json index 51d1e30..8a2d16d 100644 --- a/Shuttle/shuttle.default.json +++ b/Shuttle/shuttle.default.json @@ -1,6 +1,6 @@ { "_comments": [ - "Valid terminals include: 'Terminal.app' or 'iTerm'", + "Valid terminals include: 'Terminal.app', 'iTerm', or 'Ghostty.app'", "In the editor value change 'default' to 'nano', 'vi', or another terminal based editor.", "Hosts will also be read from your ~/.ssh/config or /etc/ssh_config file, if available", "For more information on how to configure, please see http://fitztrev.github.io/shuttle/" @@ -57,4 +57,4 @@ ] } - \ No newline at end of file + diff --git a/apple-scripts/ghostty/ghostty-current-window.applescript b/apple-scripts/ghostty/ghostty-current-window.applescript new file mode 100644 index 0000000..92b50f3 --- /dev/null +++ b/apple-scripts/ghostty/ghostty-current-window.applescript @@ -0,0 +1,28 @@ +--for testing uncomment the "on run" block +--on run +-- set argsCmd to "ps aux | grep [s]sh" +-- set argsTheme to "Homebrew" +-- set argsTitle to "Custom title" +-- scriptRun(argsCmd, argsTheme, argsTitle) +--end run + +on scriptRun(argsCmd, argsTheme, argsTitle) + set withCmd to (argsCmd) + CommandRun(withCmd) +end scriptRun + +on CommandRun(withCmd) + tell application "/Applications/Ghostty.app" + if it is not running or (count windows) is 0 then + set surfaceConfig to new surface configuration + set targetWindow to new window with configuration surfaceConfig + set targetTerminal to focused terminal of selected tab of targetWindow + else + set targetWindow to front window + set targetTerminal to focused terminal of selected tab of targetWindow + end if + input text withCmd to targetTerminal + send key "enter" to targetTerminal + focus targetTerminal + end tell +end CommandRun diff --git a/apple-scripts/ghostty/ghostty-new-tab-default.applescript b/apple-scripts/ghostty/ghostty-new-tab-default.applescript new file mode 100644 index 0000000..68d9b4e --- /dev/null +++ b/apple-scripts/ghostty/ghostty-new-tab-default.applescript @@ -0,0 +1,29 @@ +--for testing uncomment the "on run" block +--on run +-- set argsCmd to "ps aux | grep [s]sh" +-- set argsTheme to "Homebrew" +-- set argsTitle to "Custom title" +-- scriptRun(argsCmd, argsTheme, argsTitle) +--end run + +on scriptRun(argsCmd, argsTheme, argsTitle) + set withCmd to (argsCmd) + CommandRun(withCmd) +end scriptRun + +on CommandRun(withCmd) + tell application "/Applications/Ghostty.app" + set surfaceConfig to new surface configuration + if it is not running or (count windows) is 0 then + set targetWindow to new window with configuration surfaceConfig + set targetTerminal to focused terminal of selected tab of targetWindow + else + set targetWindow to front window + set targetTab to new tab in targetWindow with configuration surfaceConfig + set targetTerminal to focused terminal of targetTab + end if + input text withCmd to targetTerminal + send key "enter" to targetTerminal + focus targetTerminal + end tell +end CommandRun diff --git a/apple-scripts/ghostty/ghostty-new-window.applescript b/apple-scripts/ghostty/ghostty-new-window.applescript new file mode 100644 index 0000000..38cd9e7 --- /dev/null +++ b/apple-scripts/ghostty/ghostty-new-window.applescript @@ -0,0 +1,23 @@ +--for testing uncomment the "on run" block +--on run +-- set argsCmd to "ps aux | grep [s]sh" +-- set argsTheme to "Homebrew" +-- set argsTitle to "Custom title" +-- scriptRun(argsCmd, argsTheme, argsTitle) +--end run + +on scriptRun(argsCmd, argsTheme, argsTitle) + set withCmd to (argsCmd) + CommandRun(withCmd) +end scriptRun + +on CommandRun(withCmd) + tell application "/Applications/Ghostty.app" + set surfaceConfig to new surface configuration + set newWindow to new window with configuration surfaceConfig + set newTerminal to focused terminal of selected tab of newWindow + input text withCmd to newTerminal + send key "enter" to newTerminal + focus newTerminal + end tell +end CommandRun diff --git a/tests/test_ghostty_support.py b/tests/test_ghostty_support.py new file mode 100644 index 0000000..3be3aa8 --- /dev/null +++ b/tests/test_ghostty_support.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def read(path): + return (ROOT / path).read_text() + + +def test_default_config_mentions_ghostty(): + config = read("Shuttle/shuttle.default.json") + assert "Ghostty.app" in config + + +def test_app_delegate_routes_ghostty_to_scripts(): + app_delegate = read("Shuttle/AppDelegate.m") + assert 'rangeOfString: @"ghostty"' in app_delegate + assert 'pathForResource:@"ghostty-new-window"' in app_delegate + assert 'pathForResource:@"ghostty-current-window"' in app_delegate + assert 'pathForResource:@"ghostty-new-tab-default"' in app_delegate + + +def test_shell_commands_are_not_treated_as_urls(): + app_delegate = read("Shuttle/AppDelegate.m") + assert "if (url && [url scheme])" in app_delegate + + +def test_ghostty_scripts_are_bundled_resources(): + project = read("Shuttle.xcodeproj/project.pbxproj") + for script in [ + "ghostty-new-window.scpt", + "ghostty-current-window.scpt", + "ghostty-new-tab-default.scpt", + ]: + assert script in project + + +def test_ghostty_has_apple_event_entitlement(): + entitlements = read("Shuttle/Shuttle.entitlements") + assert "com.mitchellh.ghostty" in entitlements + + +def test_ghostty_source_scripts_use_applescript_api(): + for source in [ + "apple-scripts/ghostty/ghostty-new-window.applescript", + "apple-scripts/ghostty/ghostty-current-window.applescript", + "apple-scripts/ghostty/ghostty-new-tab-default.applescript", + ]: + script = read(source) + assert 'tell application "/Applications/Ghostty.app"' in script + assert "input text" in script + assert 'send key "enter"' in script + + +if __name__ == "__main__": + for name, value in sorted(globals().items()): + if name.startswith("test_") and callable(value): + value() From 7055c7a5de4524444010974b334c5924f5211974 Mon Sep 17 00:00:00 2001 From: wansho Date: Fri, 17 Apr 2026 14:59:37 +0800 Subject: [PATCH 2/2] docs: simplify Ghostty README note --- README.md | 43 +------------------------------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/README.md b/README.md index 44e52b4..82cf9ee 100644 --- a/README.md +++ b/README.md @@ -15,48 +15,7 @@ A simple shortcut menu for macOS 1. Download [Shuttle](http://fitztrev.github.io/shuttle/) 2. Copy to Applications -## Ghostty support - -This fork adds support for using [Ghostty](https://ghostty.org/) as the terminal launched by Shuttle. - -### What changed - -* Added `Ghostty.app` as a valid value for the top-level `terminal` setting. -* Added bundled AppleScript handlers for Ghostty: - * new window - * current window - * new tab -* Added the Ghostty Apple Events entitlement, `com.mitchellh.ghostty`. -* Fixed command launching on newer macOS versions by only treating strings with a URL scheme as URLs. Shell commands such as `ssh user@host` and custom commands such as `ssh-server ...` now continue through the terminal launcher instead of being passed to Finder as invalid URLs. - -### Configuration - -Set `terminal` to `Ghostty.app` in `~/.shuttle.json`: - -```json -{ - "terminal": "Ghostty.app", - "open_in": "new" -} -``` - -`open_in` continues to support Shuttle's existing values: - -* `new` opens a new Ghostty window. -* `tab` opens a new tab in the front Ghostty window, or creates a window if none exists. -* `current` runs the command in the focused terminal of the front Ghostty window, or creates a window if none exists. - -Per-host `inTerminal` overrides still work the same way: - -```json -{ - "name": "Example server", - "cmd": "ssh user@example.com", - "inTerminal": "tab" -} -``` - -Ghostty support requires Ghostty 1.3.0 or newer because it relies on Ghostty's macOS AppleScript API. The first time Shuttle controls Ghostty, macOS may ask for Automation permission; allow Shuttle to control Ghostty. +This fork also supports Ghostty.app. Set `"terminal": "Ghostty.app"` in your Shuttle JSON settings to use it. ## Help See the [Wiki](https://github.com/fitztrev/shuttle/wiki) pages.