From bb10aca3eeccc8e57ad25edf7d53b4cab5ccda21 Mon Sep 17 00:00:00 2001 From: Shaohua Wen Date: Mon, 20 Oct 2025 13:57:06 +0700 Subject: [PATCH 1/3] support apple sillicon --- CLAUDE.md | 123 ++++++++++++++++++++++++++++++ Shuttle.xcodeproj/project.pbxproj | 26 +++++-- Shuttle/AppDelegate.m | 16 +--- Shuttle/Shuttle-Info.plist | 4 +- 4 files changed, 148 insertions(+), 21 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2763ae8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,123 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Shuttle is a macOS native status bar application built with Objective-C and Cocoa that provides quick SSH connections and terminal commands through a menu bar interface. It integrates with both Terminal.app and iTerm2. + +## Architecture + +### Technology Stack +- **Objective-C** with **Cocoa Framework** +- **Xcode** project structure (`Shuttle.xcodeproj`) +- **AppleScript** for terminal integration +- **JSON** configuration files +- **Multi-language support**: English, Spanish, French, Chinese (Simplified) + +### Key Components +- **AppDelegate** (`Shuttle/AppDelegate.h/m`): Main application delegate handling menu creation and system integration +- **AboutWindowController** (`Shuttle/AboutWindowController.h/m`): About dialog management +- **LaunchAtLoginController** (`Shuttle/LaunchAtLoginController.h/m`): macOS login item management +- **Terminal Integration**: Compiled AppleScript files in `apple-scpt/` directory +- **Configuration**: JSON-based configuration (`~/.shuttle.json`) + +## Development Commands + +### Build & Development +```bash +# Build the Xcode project +xcodebuild -project Shuttle.xcodeproj -configuration Debug + +# Build for release +xcodebuild -project Shuttle.xcodeproj -configuration Release + +# Clean build +xcodebuild clean -project Shuttle.xcodeproj +``` + +### AppleScript Compilation +```bash +# Compile terminal integration scripts +cd apple-scripts +./compile-iTermStable.sh # For iTerm2 stable +cd apple-scripts +./compile-iTermNightly.sh # For iTerm2 nightly +cd apple-scripts +./compile-Terminal.sh # For macOS Terminal +cd apple-scripts +./compile-Virtual.sh # For virtual terminal with screen +``` + +### Configuration Testing +```bash +# Validate JSON configuration +cat ~/.shuttle.json | python -m json.tool + +# Test with sample configurations +cp tests/shuttle.json.sample ~/.shuttle.json +``` + +### Development Workflow +1. **Open**: `open Shuttle.xcodeproj` (opens in Xcode) +2. **Build**: Use Xcode GUI or `xcodebuild` commands above +3. **Test**: Run built application from Xcode or `/Applications/Shuttle.app` +4. **Debug**: Check Console.app for Shuttle logs + +## File Structure + +``` +Shuttle/ # Main source directory +├── Shuttle.xcodeproj/ # Xcode project files +├── Shuttle/ # Application source +│ ├── AppDelegate.h/m # Main application logic +│ ├── AboutWindowController.h/m +│ ├── LaunchAtLoginController.h/m +│ ├── main.m # Entry point +│ └── shuttle.default.json # Default config template +├── apple-scripts/ # Source AppleScript files +├── apple-scpt/ # Compiled AppleScript (.scpt files) +├── tests/ # Test configurations +└── *.lproj/ # Localization files +``` + +## Configuration Format + +The application uses JSON configuration (`~/.shuttle.json`) with structure: +- **terminal**: Terminal type ("Terminal.app" or "iTerm") +- **launch_at_login**: Boolean for startup behavior +- **show_ssh_config_hosts**: Boolean to parse ~/.ssh/config +- **hosts**: Array of host configurations with commands +- **menu nesting**: Supports hierarchical menu structure + +## Testing & Validation + +### Manual Testing +1. Build application +2. Copy to `/Applications/` +3. Create/modify `~/.shuttle.json` +4. Test menu functionality +5. Verify terminal integration works + +### Configuration Testing +- Use test files in `/tests/` directory +- Validate JSON syntax before deployment +- Test with various terminal applications + +## Special Considerations + +### Permissions +- Requires **NSAppleEventsUsageDescription** for AppleScript execution +- Uses macOS app sandboxing +- Requires Accessibility permissions for terminal automation + +### Localization +- Uses `.lproj` directories for internationalization +- Localized strings in `*.strings` files +- Interface files in `*.xib` format + +### Terminal Integration +- Supports Terminal.app, iTerm2 (stable/nightly/legacy) +- Uses AppleScript for terminal automation +- Handles SSH connections and custom commands +- Supports window/tab management preferences \ No newline at end of file diff --git a/Shuttle.xcodeproj/project.pbxproj b/Shuttle.xcodeproj/project.pbxproj index d174fef..3222d0e 100644 --- a/Shuttle.xcodeproj/project.pbxproj +++ b/Shuttle.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -398,9 +398,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.8; + MACOSX_DEPLOYMENT_TARGET = 10.15; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; + ARCHS = "$(ARCHS_STANDARD)"; + VALID_ARCHS = "arm64 x86_64"; + SUPPORTS_MACCATALYST = NO; + EXCLUDED_ARCHS = ""; }; name = Debug; }; @@ -409,7 +413,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -431,7 +435,7 @@ COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_C_LANGUAGE_STANDARD = gnu17; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -440,8 +444,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.8; + MACOSX_DEPLOYMENT_TARGET = 10.15; SDKROOT = macosx; + ARCHS = "$(ARCHS_STANDARD)"; + VALID_ARCHS = "arm64 x86_64"; + SUPPORTS_MACCATALYST = NO; + EXCLUDED_ARCHS = ""; }; name = Release; }; @@ -454,10 +462,12 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Shuttle/Shuttle-Prefix.pch"; INFOPLIST_FILE = "Shuttle/Shuttle-Info.plist"; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = "shuttle.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; + ARCHS = "$(ARCHS_STANDARD)"; + VALID_ARCHS = "arm64 x86_64"; }; name = Debug; }; @@ -470,10 +480,12 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Shuttle/Shuttle-Prefix.pch"; INFOPLIST_FILE = "Shuttle/Shuttle-Info.plist"; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = "shuttle.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; + ARCHS = "$(ARCHS_STANDARD)"; + VALID_ARCHS = "arm64 x86_64"; }; name = Release; }; diff --git a/Shuttle/AppDelegate.m b/Shuttle/AppDelegate.m index 2f77935..7af0b5c 100644 --- a/Shuttle/AppDelegate.m +++ b/Shuttle/AppDelegate.m @@ -72,19 +72,9 @@ - (void) awakeFromNib { [statusItem setMenu:menu]; [statusItem setImage: regularIcon]; - // Check for AppKit Version, add support for darkmode if > 10.9 - BOOL oldAppKitVersion = (floor(NSAppKitVersionNumber) <= 1265); - - // 10.10 or higher, dont load the alt image let OS X style it. - if (!oldAppKitVersion) - { - regularIcon.template = YES; - } - // Load the alt image for OS X < 10.10 - else{ - [statusItem setHighlightMode:YES]; - [statusItem setAlternateImage: altIcon]; - } + // Modern macOS versions (10.10+) support dynamic icons + // Since we now require 10.15+, we can always use template images + regularIcon.template = YES; launchAtLoginController = [[LaunchAtLoginController alloc] init]; // Needed to trigger the menuWillOpen event diff --git a/Shuttle/Shuttle-Info.plist b/Shuttle/Shuttle-Info.plist index b760d03..dda7449 100644 --- a/Shuttle/Shuttle-Info.plist +++ b/Shuttle/Shuttle-Info.plist @@ -27,7 +27,9 @@ LSUIElement NSAppleEventsUsageDescription - Shuttle needs Automation privileges in Security & Privacy to run applescripts + Shuttle needs Automation privileges to control Terminal.app and iTerm2 for SSH connections + NSAppleEventsUsageDescriptionAutomation + Shuttle uses AppleScript to open SSH connections in your preferred terminal application NSHumanReadableCopyright Copyright © 2016 Trevor Fitzgerald. All rights reserved. NSMainNibFile From a74be74c5e029cd6ae8e8cff32ab85e9f5f890d5 Mon Sep 17 00:00:00 2001 From: Shaohua Wen Date: Mon, 20 Oct 2025 14:18:05 +0700 Subject: [PATCH 2/3] support apple silicon --- CLAUDE.md | 123 ------------------------------------------------------ 1 file changed, 123 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 2763ae8..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,123 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -Shuttle is a macOS native status bar application built with Objective-C and Cocoa that provides quick SSH connections and terminal commands through a menu bar interface. It integrates with both Terminal.app and iTerm2. - -## Architecture - -### Technology Stack -- **Objective-C** with **Cocoa Framework** -- **Xcode** project structure (`Shuttle.xcodeproj`) -- **AppleScript** for terminal integration -- **JSON** configuration files -- **Multi-language support**: English, Spanish, French, Chinese (Simplified) - -### Key Components -- **AppDelegate** (`Shuttle/AppDelegate.h/m`): Main application delegate handling menu creation and system integration -- **AboutWindowController** (`Shuttle/AboutWindowController.h/m`): About dialog management -- **LaunchAtLoginController** (`Shuttle/LaunchAtLoginController.h/m`): macOS login item management -- **Terminal Integration**: Compiled AppleScript files in `apple-scpt/` directory -- **Configuration**: JSON-based configuration (`~/.shuttle.json`) - -## Development Commands - -### Build & Development -```bash -# Build the Xcode project -xcodebuild -project Shuttle.xcodeproj -configuration Debug - -# Build for release -xcodebuild -project Shuttle.xcodeproj -configuration Release - -# Clean build -xcodebuild clean -project Shuttle.xcodeproj -``` - -### AppleScript Compilation -```bash -# Compile terminal integration scripts -cd apple-scripts -./compile-iTermStable.sh # For iTerm2 stable -cd apple-scripts -./compile-iTermNightly.sh # For iTerm2 nightly -cd apple-scripts -./compile-Terminal.sh # For macOS Terminal -cd apple-scripts -./compile-Virtual.sh # For virtual terminal with screen -``` - -### Configuration Testing -```bash -# Validate JSON configuration -cat ~/.shuttle.json | python -m json.tool - -# Test with sample configurations -cp tests/shuttle.json.sample ~/.shuttle.json -``` - -### Development Workflow -1. **Open**: `open Shuttle.xcodeproj` (opens in Xcode) -2. **Build**: Use Xcode GUI or `xcodebuild` commands above -3. **Test**: Run built application from Xcode or `/Applications/Shuttle.app` -4. **Debug**: Check Console.app for Shuttle logs - -## File Structure - -``` -Shuttle/ # Main source directory -├── Shuttle.xcodeproj/ # Xcode project files -├── Shuttle/ # Application source -│ ├── AppDelegate.h/m # Main application logic -│ ├── AboutWindowController.h/m -│ ├── LaunchAtLoginController.h/m -│ ├── main.m # Entry point -│ └── shuttle.default.json # Default config template -├── apple-scripts/ # Source AppleScript files -├── apple-scpt/ # Compiled AppleScript (.scpt files) -├── tests/ # Test configurations -└── *.lproj/ # Localization files -``` - -## Configuration Format - -The application uses JSON configuration (`~/.shuttle.json`) with structure: -- **terminal**: Terminal type ("Terminal.app" or "iTerm") -- **launch_at_login**: Boolean for startup behavior -- **show_ssh_config_hosts**: Boolean to parse ~/.ssh/config -- **hosts**: Array of host configurations with commands -- **menu nesting**: Supports hierarchical menu structure - -## Testing & Validation - -### Manual Testing -1. Build application -2. Copy to `/Applications/` -3. Create/modify `~/.shuttle.json` -4. Test menu functionality -5. Verify terminal integration works - -### Configuration Testing -- Use test files in `/tests/` directory -- Validate JSON syntax before deployment -- Test with various terminal applications - -## Special Considerations - -### Permissions -- Requires **NSAppleEventsUsageDescription** for AppleScript execution -- Uses macOS app sandboxing -- Requires Accessibility permissions for terminal automation - -### Localization -- Uses `.lproj` directories for internationalization -- Localized strings in `*.strings` files -- Interface files in `*.xib` format - -### Terminal Integration -- Supports Terminal.app, iTerm2 (stable/nightly/legacy) -- Uses AppleScript for terminal automation -- Handles SSH connections and custom commands -- Supports window/tab management preferences \ No newline at end of file From ae78e68541d63760774a06d314b719cd0c5b4bb5 Mon Sep 17 00:00:00 2001 From: Shaohua Wen Date: Mon, 20 Oct 2025 15:49:05 +0700 Subject: [PATCH 3/3] add support for newer macos --- Shuttle.entitlements | 15 +++++++++++++++ Shuttle/AppDelegate.m | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 Shuttle.entitlements diff --git a/Shuttle.entitlements b/Shuttle.entitlements new file mode 100644 index 0000000..e7f7f62 --- /dev/null +++ b/Shuttle.entitlements @@ -0,0 +1,15 @@ + + + + + com.apple.security.automation.apple-events + + com.apple.security.temporary-exception.apple-events + + com.apple.Terminal + com.googlecode.iterm2 + + com.apple.security.get-task-allow + + + \ No newline at end of file diff --git a/Shuttle/AppDelegate.m b/Shuttle/AppDelegate.m index 7af0b5c..d71dfee 100644 --- a/Shuttle/AppDelegate.m +++ b/Shuttle/AppDelegate.m @@ -6,6 +6,9 @@ #import "AppDelegate.h" #import "AboutWindowController.h" +// Add version detection macro for macOS compatibility +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[NSProcessInfo processInfo] operatingSystemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) + @implementation AppDelegate - (void) awakeFromNib { @@ -553,11 +556,12 @@ - (void) openHost:(NSMenuItem *) sender { else { passParameters = @[escapedObject, terminalTitle]; } - // Check if Url - if (url) + // Modern macOS permission prompt: System will automatically prompt on first attempt + + // Check if url is valid + if (url && [self isValidURL:escapedObject]) { [[NSWorkspace sharedWorkspace] openURL:url]; - } //If the JSON file is set to use iTerm else if ( [terminalPref rangeOfString: @"iterm"].location !=NSNotFound ) { @@ -713,6 +717,38 @@ - (IBAction)showImportPanel:(id)sender { } +// New: Simplified permission check - relies on system prompts +- (BOOL)checkAppleEventsPermission { + // In macOS 10.15+, system will automatically prompt permissions, return YES + return YES; +} + +// New: Simplified permission request - provide user guidance +- (void)requestAppleEventsPermission { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Permission Required"]; + [alert setInformativeText:@"Shuttle requires Accessibility permissions to control terminal applications. Please go to System Preferences → Security & Privacy → Accessibility, add and enable Shuttle."]; + [alert addButtonWithTitle:@"Open System Preferences"]; + [alert addButtonWithTitle:@"Later"]; + + if ([alert runModal] == NSAlertFirstButtonReturn) { + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"]]; + } +} + +// New: Enhanced URL validation +- (BOOL)isValidURL:(NSString *)string { + NSURL *url = [NSURL URLWithString:string]; + if (!url) return NO; + + NSString *scheme = [url scheme]; + if (!scheme) return NO; + + // Only allow standard protocols + NSArray *validSchemes = @[@"http", @"https", @"ftp", @"file", @"ssh", @"telnet"]; + return [validSchemes containsObject:scheme.lowercaseString]; +} + -(void) throwError:(NSString*)errorMessage additionalInfo:(NSString*)errorInfo continueOnErrorOption:(BOOL)continueOption { NSAlert *alert = [[NSAlert alloc] init]; [alert setInformativeText:errorInfo];