mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
Merge pull request #1298 from mitchellh/ios
build: add iOS-compatible libghostty build to xcframework
This commit is contained in:
2
.github/workflows/release-tip.yml
vendored
2
.github/workflows/release-tip.yml
vendored
@ -88,7 +88,7 @@ jobs:
|
||||
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
|
||||
# Nix breaks xcodebuild so this has to be run outside.
|
||||
- name: Build Ghostty.app
|
||||
run: cd macos && xcodebuild -configuration Release
|
||||
run: cd macos && xcodebuild -target Ghostty -configuration Release
|
||||
|
||||
# We inject the "build number" as simply the number of commits since HEAD.
|
||||
# This will be a monotonically always increasing build number that we use.
|
||||
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -88,7 +88,13 @@ jobs:
|
||||
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
|
||||
# Nix breaks xcodebuild so this has to be run outside.
|
||||
- name: Build Ghostty.app
|
||||
run: cd macos && xcodebuild
|
||||
run: cd macos && xcodebuild -target Ghostty
|
||||
|
||||
# Build the iOS target without code signing just to verify it works.
|
||||
- name: Build Ghostty iOS
|
||||
run: |
|
||||
cd macos
|
||||
xcodebuild -target Ghostty-iOS "CODE_SIGNING_ALLOWED=NO"
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-2019
|
||||
|
342
build.zig
342
build.zig
@ -37,25 +37,7 @@ const app_version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 };
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const target = target: {
|
||||
var result = b.standardTargetOptions(.{});
|
||||
|
||||
// On macOS, we specify a minimum supported version. This is important
|
||||
// to set since header files will use this to determine the availability
|
||||
// of certain APIs and I believe it is also encoded in the Mach-O
|
||||
// binaries.
|
||||
if (result.result.os.tag == .macos and
|
||||
result.query.os_version_min == null)
|
||||
{
|
||||
result.query.os_version_min = .{ .semver = .{
|
||||
.major = 12,
|
||||
.minor = 0,
|
||||
.patch = 0,
|
||||
} };
|
||||
}
|
||||
|
||||
break :target result;
|
||||
};
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
const wasm_target: WasmTarget = .browser;
|
||||
|
||||
@ -438,92 +420,55 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
// On Mac we can build the embedding library. This only handles the macOS lib.
|
||||
if (builtin.target.isDarwin() and target.result.os.tag == .macos) {
|
||||
// Modify our build configuration for macOS builds.
|
||||
const macos_config: BuildConfig = config: {
|
||||
var copy = config;
|
||||
|
||||
// Always static for the macOS app because we want all of our
|
||||
// dependencies in a fat static library.
|
||||
copy.static = true;
|
||||
|
||||
break :config copy;
|
||||
};
|
||||
|
||||
const static_lib_aarch64 = lib: {
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "ghostty",
|
||||
.root_source_file = .{ .path = "src/main_c.zig" },
|
||||
.target = b.resolveTargetQuery(.{
|
||||
.cpu_arch = .aarch64,
|
||||
.os_tag = .macos,
|
||||
.os_version_min = target.query.os_version_min,
|
||||
}),
|
||||
.optimize = optimize,
|
||||
});
|
||||
lib.bundle_compiler_rt = true;
|
||||
lib.linkLibC();
|
||||
lib.root_module.addOptions("build_options", exe_options);
|
||||
|
||||
// Create a single static lib with all our dependencies merged
|
||||
var lib_list = try addDeps(b, lib, macos_config);
|
||||
try lib_list.append(lib.getEmittedBin());
|
||||
const libtool = LibtoolStep.create(b, .{
|
||||
.name = "ghostty",
|
||||
.out_name = "libghostty-aarch64-fat.a",
|
||||
.sources = lib_list.items,
|
||||
});
|
||||
libtool.step.dependOn(&lib.step);
|
||||
b.default_step.dependOn(libtool.step);
|
||||
|
||||
break :lib libtool;
|
||||
};
|
||||
|
||||
const static_lib_x86_64 = lib: {
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "ghostty",
|
||||
.root_source_file = .{ .path = "src/main_c.zig" },
|
||||
.target = b.resolveTargetQuery(.{
|
||||
.cpu_arch = .x86_64,
|
||||
.os_tag = .macos,
|
||||
.os_version_min = target.query.os_version_min,
|
||||
}),
|
||||
.optimize = optimize,
|
||||
});
|
||||
lib.bundle_compiler_rt = true;
|
||||
lib.linkLibC();
|
||||
lib.root_module.addOptions("build_options", exe_options);
|
||||
|
||||
// Create a single static lib with all our dependencies merged
|
||||
var lib_list = try addDeps(b, lib, macos_config);
|
||||
try lib_list.append(lib.getEmittedBin());
|
||||
const libtool = LibtoolStep.create(b, .{
|
||||
.name = "ghostty",
|
||||
.out_name = "libghostty-x86_64-fat.a",
|
||||
.sources = lib_list.items,
|
||||
});
|
||||
libtool.step.dependOn(&lib.step);
|
||||
b.default_step.dependOn(libtool.step);
|
||||
|
||||
break :lib libtool;
|
||||
};
|
||||
|
||||
const static_lib_universal = LipoStep.create(b, .{
|
||||
.name = "ghostty",
|
||||
.out_name = "libghostty.a",
|
||||
.input_a = static_lib_aarch64.output,
|
||||
.input_b = static_lib_x86_64.output,
|
||||
});
|
||||
static_lib_universal.step.dependOn(static_lib_aarch64.step);
|
||||
static_lib_universal.step.dependOn(static_lib_x86_64.step);
|
||||
// Create the universal macOS lib.
|
||||
const macos_lib_step, const macos_lib_path = try createMacOSLib(
|
||||
b,
|
||||
optimize,
|
||||
config,
|
||||
exe_options,
|
||||
);
|
||||
|
||||
// Add our library to zig-out
|
||||
const lib_install = b.addInstallLibFile(
|
||||
static_lib_universal.output,
|
||||
"libghostty.a",
|
||||
macos_lib_path,
|
||||
"libghostty-macos.a",
|
||||
);
|
||||
b.getInstallStep().dependOn(&lib_install.step);
|
||||
|
||||
// Copy our ghostty.h to include
|
||||
// Create the universal iOS lib.
|
||||
const ios_lib_step, const ios_lib_path = try createIOSLib(
|
||||
b,
|
||||
null,
|
||||
optimize,
|
||||
config,
|
||||
exe_options,
|
||||
);
|
||||
|
||||
// Add our library to zig-out
|
||||
const ios_lib_install = b.addInstallLibFile(
|
||||
ios_lib_path,
|
||||
"libghostty-ios.a",
|
||||
);
|
||||
b.getInstallStep().dependOn(&ios_lib_install.step);
|
||||
|
||||
// Create the iOS simulator lib.
|
||||
const ios_sim_lib_step, const ios_sim_lib_path = try createIOSLib(
|
||||
b,
|
||||
.simulator,
|
||||
optimize,
|
||||
config,
|
||||
exe_options,
|
||||
);
|
||||
|
||||
// Add our library to zig-out
|
||||
const ios_sim_lib_install = b.addInstallLibFile(
|
||||
ios_sim_lib_path,
|
||||
"libghostty-ios-simulator.a",
|
||||
);
|
||||
b.getInstallStep().dependOn(&ios_sim_lib_install.step);
|
||||
|
||||
// Copy our ghostty.h to include. The header file is shared by
|
||||
// all embedded targets.
|
||||
const header_install = b.addInstallHeaderFile(
|
||||
"include/ghostty.h",
|
||||
"ghostty.h",
|
||||
@ -535,10 +480,25 @@ pub fn build(b: *std.Build) !void {
|
||||
const xcframework = XCFrameworkStep.create(b, .{
|
||||
.name = "GhosttyKit",
|
||||
.out_path = "macos/GhosttyKit.xcframework",
|
||||
.library = static_lib_universal.output,
|
||||
.headers = .{ .path = "include" },
|
||||
.libraries = &.{
|
||||
.{
|
||||
.library = macos_lib_path,
|
||||
.headers = .{ .path = "include" },
|
||||
},
|
||||
.{
|
||||
.library = ios_lib_path,
|
||||
.headers = .{ .path = "include" },
|
||||
},
|
||||
.{
|
||||
.library = ios_sim_lib_path,
|
||||
.headers = .{ .path = "include" },
|
||||
},
|
||||
},
|
||||
});
|
||||
xcframework.step.dependOn(static_lib_universal.step);
|
||||
xcframework.step.dependOn(ios_lib_step);
|
||||
xcframework.step.dependOn(ios_sim_lib_step);
|
||||
xcframework.step.dependOn(macos_lib_step);
|
||||
xcframework.step.dependOn(&header_install.step);
|
||||
b.default_step.dependOn(xcframework.step);
|
||||
}
|
||||
|
||||
@ -672,6 +632,175 @@ pub fn build(b: *std.Build) !void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum OS version for the given OS tag. This shouldn't
|
||||
/// be used generally, it should only be used for Darwin-based OS currently.
|
||||
fn osVersionMin(tag: std.Target.Os.Tag) ?std.Target.Query.OsVersion {
|
||||
return switch (tag) {
|
||||
// The lowest supported version of macOS is 12.x because
|
||||
// this is the first version to support Apple Silicon so it is
|
||||
// the earliest version we can virtualize to test (I only have
|
||||
// an Apple Silicon machine for macOS).
|
||||
.macos => .{ .semver = .{
|
||||
.major = 12,
|
||||
.minor = 0,
|
||||
.patch = 0,
|
||||
} },
|
||||
|
||||
// iOS 17 picked arbitrarily
|
||||
.ios => .{ .semver = .{
|
||||
.major = 17,
|
||||
.minor = 0,
|
||||
.patch = 0,
|
||||
} },
|
||||
|
||||
// This should never happen currently. If we add a new target then
|
||||
// we should add a new case here.
|
||||
else => @panic("unhandled os version min os tag"),
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a universal macOS libghostty library and returns the path
|
||||
/// to the final library.
|
||||
///
|
||||
/// The library is always a fat static library currently because this is
|
||||
/// expected to be used directly with Xcode and Swift. In the future, we
|
||||
/// probably want to change this because it makes it harder to use the
|
||||
/// library in other contexts.
|
||||
fn createMacOSLib(
|
||||
b: *std.Build,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
config: BuildConfig,
|
||||
exe_options: *std.Build.Step.Options,
|
||||
) !struct { *std.Build.Step, std.Build.LazyPath } {
|
||||
// Modify our build configuration for macOS builds.
|
||||
const macos_config: BuildConfig = config: {
|
||||
var copy = config;
|
||||
|
||||
// Always static for the macOS app because we want all of our
|
||||
// dependencies in a fat static library.
|
||||
copy.static = true;
|
||||
|
||||
break :config copy;
|
||||
};
|
||||
|
||||
const static_lib_aarch64 = lib: {
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "ghostty",
|
||||
.root_source_file = .{ .path = "src/main_c.zig" },
|
||||
.target = b.resolveTargetQuery(.{
|
||||
.cpu_arch = .aarch64,
|
||||
.os_tag = .macos,
|
||||
.os_version_min = osVersionMin(.macos),
|
||||
}),
|
||||
.optimize = optimize,
|
||||
});
|
||||
lib.bundle_compiler_rt = true;
|
||||
lib.linkLibC();
|
||||
lib.root_module.addOptions("build_options", exe_options);
|
||||
|
||||
// Create a single static lib with all our dependencies merged
|
||||
var lib_list = try addDeps(b, lib, macos_config);
|
||||
try lib_list.append(lib.getEmittedBin());
|
||||
const libtool = LibtoolStep.create(b, .{
|
||||
.name = "ghostty",
|
||||
.out_name = "libghostty-aarch64-fat.a",
|
||||
.sources = lib_list.items,
|
||||
});
|
||||
libtool.step.dependOn(&lib.step);
|
||||
b.default_step.dependOn(libtool.step);
|
||||
|
||||
break :lib libtool;
|
||||
};
|
||||
|
||||
const static_lib_x86_64 = lib: {
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "ghostty",
|
||||
.root_source_file = .{ .path = "src/main_c.zig" },
|
||||
.target = b.resolveTargetQuery(.{
|
||||
.cpu_arch = .x86_64,
|
||||
.os_tag = .macos,
|
||||
.os_version_min = osVersionMin(.macos),
|
||||
}),
|
||||
.optimize = optimize,
|
||||
});
|
||||
lib.bundle_compiler_rt = true;
|
||||
lib.linkLibC();
|
||||
lib.root_module.addOptions("build_options", exe_options);
|
||||
|
||||
// Create a single static lib with all our dependencies merged
|
||||
var lib_list = try addDeps(b, lib, macos_config);
|
||||
try lib_list.append(lib.getEmittedBin());
|
||||
const libtool = LibtoolStep.create(b, .{
|
||||
.name = "ghostty",
|
||||
.out_name = "libghostty-x86_64-fat.a",
|
||||
.sources = lib_list.items,
|
||||
});
|
||||
libtool.step.dependOn(&lib.step);
|
||||
b.default_step.dependOn(libtool.step);
|
||||
|
||||
break :lib libtool;
|
||||
};
|
||||
|
||||
const static_lib_universal = LipoStep.create(b, .{
|
||||
.name = "ghostty",
|
||||
.out_name = "libghostty.a",
|
||||
.input_a = static_lib_aarch64.output,
|
||||
.input_b = static_lib_x86_64.output,
|
||||
});
|
||||
static_lib_universal.step.dependOn(static_lib_aarch64.step);
|
||||
static_lib_universal.step.dependOn(static_lib_x86_64.step);
|
||||
|
||||
return .{
|
||||
static_lib_universal.step,
|
||||
static_lib_universal.output,
|
||||
};
|
||||
}
|
||||
|
||||
/// Create an Apple iOS/iPadOS build.
|
||||
fn createIOSLib(
|
||||
b: *std.Build,
|
||||
abi: ?std.Target.Abi,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
config: BuildConfig,
|
||||
exe_options: *std.Build.Step.Options,
|
||||
) !struct { *std.Build.Step, std.Build.LazyPath } {
|
||||
const ios_config: BuildConfig = config: {
|
||||
var copy = config;
|
||||
copy.static = true;
|
||||
break :config copy;
|
||||
};
|
||||
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "ghostty",
|
||||
.root_source_file = .{ .path = "src/main_c.zig" },
|
||||
.optimize = optimize,
|
||||
.target = b.resolveTargetQuery(.{
|
||||
.cpu_arch = .aarch64,
|
||||
.os_tag = .ios,
|
||||
.os_version_min = osVersionMin(.ios),
|
||||
.abi = abi,
|
||||
}),
|
||||
});
|
||||
lib.bundle_compiler_rt = true;
|
||||
lib.linkLibC();
|
||||
lib.root_module.addOptions("build_options", exe_options);
|
||||
|
||||
// Create a single static lib with all our dependencies merged
|
||||
var lib_list = try addDeps(b, lib, ios_config);
|
||||
try lib_list.append(lib.getEmittedBin());
|
||||
const libtool = LibtoolStep.create(b, .{
|
||||
.name = "ghostty",
|
||||
.out_name = "libghostty-ios-fat.a",
|
||||
.sources = lib_list.items,
|
||||
});
|
||||
libtool.step.dependOn(&lib.step);
|
||||
|
||||
return .{
|
||||
libtool.step,
|
||||
libtool.output,
|
||||
};
|
||||
}
|
||||
|
||||
/// Used to keep track of a list of file sources.
|
||||
const LazyPathList = std.ArrayList(std.Build.LazyPath);
|
||||
|
||||
@ -830,7 +959,14 @@ fn addDeps(
|
||||
|
||||
// Mac Stuff
|
||||
if (step.rootModuleTarget().isDarwin()) {
|
||||
step.root_module.addImport("objc", objc_dep.module("objc"));
|
||||
// This is a bit of a hack that should probably be fixed upstream
|
||||
// in zig-objc, but we need to add the apple SDK paths to the
|
||||
// zig-objc module so that it can find the objc runtime headers.
|
||||
const module = objc_dep.module("objc");
|
||||
module.resolved_target = step.root_module.resolved_target;
|
||||
try @import("apple_sdk").addPaths(b, module);
|
||||
step.root_module.addImport("objc", module);
|
||||
|
||||
step.root_module.addImport("macos", macos_dep.module("macos"));
|
||||
step.linkLibrary(macos_dep.artifact("macos"));
|
||||
try static_libs.append(macos_dep.artifact("macos").getEmittedBin());
|
||||
|
@ -5,8 +5,8 @@
|
||||
.dependencies = .{
|
||||
// Zig libs
|
||||
.libxev = .{
|
||||
.url = "https://github.com/mitchellh/libxev/archive/74bc7aea4a8f88210f0ad4215108613ab7e7af1a.tar.gz",
|
||||
.hash = "122029743e5d96aa1b57a1b99ff58bf13ff9ed6d8f624ac3ae8074062feb91c5bd8d",
|
||||
.url = "https://github.com/mitchellh/libxev/archive/4e6781895e4e6c477597c8c2713d36cd82b57d07.tar.gz",
|
||||
.hash = "12203f87e00caa6c07c02a748f234a5c0ee2ca5c334ec464e88810d93e7b5495a56f",
|
||||
},
|
||||
.mach_glfw = .{
|
||||
.url = "https://github.com/der-teufel-programming/mach-glfw/archive/a9aae000cdc104dabe75d829ff9dab6809e47604.tar.gz",
|
||||
|
@ -1,5 +1,11 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_512x512@2x@2x 1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_16x16.png",
|
||||
"idiom" : "mac",
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
@ -11,6 +11,9 @@
|
||||
552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; };
|
||||
8503D7C72A549C66006CFF3D /* FullScreenHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */; };
|
||||
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
||||
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
||||
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
||||
A514C8D82B54DC6800493A16 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
|
||||
A51B78472AF4B58B00F3EDB9 /* TerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */; };
|
||||
A51BFC1E2B2FB5CE00E92F16 /* About.xib in Resources */ = {isa = PBXBuildFile; fileRef = A51BFC1D2B2FB5CE00E92F16 /* About.xib */; };
|
||||
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */; };
|
||||
@ -20,8 +23,12 @@
|
||||
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */; };
|
||||
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; };
|
||||
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; };
|
||||
A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
|
||||
A53D0C942B53B43700305CE6 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C932B53B43700305CE6 /* iOSApp.swift */; };
|
||||
A53D0C952B53B4D800305CE6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||
A53D0C9B2B543F3B00305CE6 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
|
||||
A53D0C9C2B543F7B00305CE6 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB729B6F53A0055DE60 /* Package.swift */; };
|
||||
A55685E029A03A9F004303CE /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55685DF29A03A9F004303CE /* AppError.swift */; };
|
||||
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB529B6F47F0055DE60 /* AppState.swift */; };
|
||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB729B6F53A0055DE60 /* Package.swift */; };
|
||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */; };
|
||||
A56B880B2A840447007A0E29 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A56B880A2A840447007A0E29 /* Carbon.framework */; };
|
||||
@ -60,6 +67,7 @@
|
||||
552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = "<group>"; };
|
||||
8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenHandler.swift; sourceTree = "<group>"; };
|
||||
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
|
||||
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; };
|
||||
A51BFC1D2B2FB5CE00E92F16 /* About.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = About.xib; sourceTree = "<group>"; };
|
||||
A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = "<group>"; };
|
||||
@ -69,8 +77,9 @@
|
||||
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Input.swift; sourceTree = "<group>"; };
|
||||
A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
|
||||
A53D0C932B53B43700305CE6 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
|
||||
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.App.swift; sourceTree = "<group>"; };
|
||||
A55685DF29A03A9F004303CE /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
|
||||
A55B7BB529B6F47F0055DE60 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
||||
A55B7BB729B6F53A0055DE60 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
||||
A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
|
||||
A56B880A2A840447007A0E29 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
|
||||
@ -99,6 +108,7 @@
|
||||
A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
||||
A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalRestorable.swift; sourceTree = "<group>"; };
|
||||
A5D0AF3C2B37804400D21823 /* CodableBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableBridge.swift; sourceTree = "<group>"; };
|
||||
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ghostty-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
|
||||
A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ClipboardConfirmation.xib; sourceTree = "<group>"; };
|
||||
A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationController.swift; sourceTree = "<group>"; };
|
||||
@ -117,6 +127,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
A5D4499A2B53AE7B000F5B83 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@ -175,12 +193,37 @@
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A54CD6ED299BEB14008C95BB /* Sources */ = {
|
||||
A53D0C912B53B41900305CE6 /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A53D0C962B53B57D00305CE6 /* macOS */,
|
||||
A53D0C922B53B42000305CE6 /* iOS */,
|
||||
);
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A53D0C922B53B42000305CE6 /* iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A53D0C932B53B43700305CE6 /* iOSApp.swift */,
|
||||
);
|
||||
path = iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A53D0C962B53B57D00305CE6 /* macOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A5FEB2FF2ABB69450068369E /* main.swift */,
|
||||
A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */,
|
||||
857F63802A5E64F200CA4815 /* MainMenu.xib */,
|
||||
);
|
||||
path = macOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A54CD6ED299BEB14008C95BB /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A53D0C912B53B41900305CE6 /* App */,
|
||||
A53426362A7DC53000EBB7A2 /* Features */,
|
||||
A534263D2A7DCBB000EBB7A2 /* Helpers */,
|
||||
A55B7BB429B6F4410055DE60 /* Ghostty */,
|
||||
@ -192,9 +235,10 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A55B7BB729B6F53A0055DE60 /* Package.swift */,
|
||||
A55B7BB529B6F47F0055DE60 /* AppState.swift */,
|
||||
A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */,
|
||||
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */,
|
||||
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */,
|
||||
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */,
|
||||
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */,
|
||||
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */,
|
||||
A59630A32AF059BB00D64628 /* Ghostty.SplitNode.swift */,
|
||||
@ -255,6 +299,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A5B30531299BEAAA0047F10C /* Ghostty.app */,
|
||||
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -310,6 +355,23 @@
|
||||
productReference = A5B30531299BEAAA0047F10C /* Ghostty.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = A5D449AB2B53AE7B000F5B83 /* Build configuration list for PBXNativeTarget "Ghostty-iOS" */;
|
||||
buildPhases = (
|
||||
A5D449992B53AE7B000F5B83 /* Sources */,
|
||||
A5D4499A2B53AE7B000F5B83 /* Frameworks */,
|
||||
A5D4499B2B53AE7B000F5B83 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Ghostty-iOS";
|
||||
productName = "Ghostty-iOS";
|
||||
productReference = A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@ -317,12 +379,15 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1420;
|
||||
LastSwiftUpdateCheck = 1520;
|
||||
LastUpgradeCheck = 1420;
|
||||
TargetAttributes = {
|
||||
A5B30530299BEAAA0047F10C = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
};
|
||||
A5D4499C2B53AE7B000F5B83 = {
|
||||
CreatedOnToolsVersion = 15.2;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = A5B3052C299BEAAA0047F10C /* Build configuration list for PBXProject "Ghostty" */;
|
||||
@ -342,6 +407,7 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
A5B30530299BEAAA0047F10C /* Ghostty */,
|
||||
A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -363,6 +429,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
A5D4499B2B53AE7B000F5B83 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A53D0C952B53B4D800305CE6 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@ -371,6 +445,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */,
|
||||
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */,
|
||||
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
||||
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
|
||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
||||
@ -390,7 +465,6 @@
|
||||
A5FEB3002ABB69450068369E /* main.swift in Sources */,
|
||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TerminalWindow.swift in Sources */,
|
||||
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */,
|
||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
|
||||
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */,
|
||||
@ -403,6 +477,18 @@
|
||||
A596309E2AEE1D6C00D64628 /* TerminalView.swift in Sources */,
|
||||
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */,
|
||||
A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */,
|
||||
A514C8D82B54DC6800493A16 /* Ghostty.App.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
A5D449992B53AE7B000F5B83 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A53D0C942B53B43700305CE6 /* iOSApp.swift in Sources */,
|
||||
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */,
|
||||
A53D0C9C2B543F7B00305CE6 /* Package.swift in Sources */,
|
||||
A53D0C9B2B543F3B00305CE6 /* Ghostty.App.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -682,6 +768,123 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
A5D449A82B53AE7B000F5B83 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 0.1;
|
||||
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-ios";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
A5D449A92B53AE7B000F5B83 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 0.1;
|
||||
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-ios";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
A5D449AA2B53AE7B000F5B83 /* ReleaseLocal */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 0.1;
|
||||
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-ios";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = ReleaseLocal;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@ -705,6 +908,16 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = ReleaseLocal;
|
||||
};
|
||||
A5D449AB2B53AE7B000F5B83 /* Build configuration list for PBXNativeTarget "Ghostty-iOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A5D449A82B53AE7B000F5B83 /* Debug */,
|
||||
A5D449A92B53AE7B000F5B83 /* Release */,
|
||||
A5D449AA2B53AE7B000F5B83 /* ReleaseLocal */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = ReleaseLocal;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
|
33
macos/Sources/App/iOS/iOSApp.swift
Normal file
33
macos/Sources/App/iOS/iOSApp.swift
Normal file
@ -0,0 +1,33 @@
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct Ghostty_iOSApp: App {
|
||||
@StateObject private var ghostty_app = Ghostty.App()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
iOS_ContentView()
|
||||
.environmentObject(ghostty_app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct iOS_ContentView: View {
|
||||
@EnvironmentObject private var ghostty_app: Ghostty.App
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image("AppIconImage")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(maxHeight: 96)
|
||||
Text("Ghostty")
|
||||
Text("State: \(ghostty_app.readiness.rawValue)")
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
iOS_ContentView()
|
||||
}
|
@ -8,7 +8,7 @@ class AppDelegate: NSObject,
|
||||
ObservableObject,
|
||||
NSApplicationDelegate,
|
||||
UNUserNotificationCenterDelegate,
|
||||
GhosttyAppStateDelegate
|
||||
GhosttyAppDelegate
|
||||
{
|
||||
// The application logger. We should probably move this at some point to a dedicated
|
||||
// class/struct but for now it lives here! 🤷♂️
|
||||
@ -62,7 +62,7 @@ class AppDelegate: NSObject,
|
||||
private var applicationHasBecomeActive: Bool = false
|
||||
|
||||
/// The ghostty global state. Only one per process.
|
||||
let ghostty: Ghostty.AppState = Ghostty.AppState()
|
||||
let ghostty: Ghostty.App = Ghostty.App()
|
||||
|
||||
/// Manages our terminal windows.
|
||||
let terminalManager: TerminalManager
|
||||
@ -143,7 +143,7 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return ghostty.shouldQuitAfterLastWindowClosed
|
||||
return ghostty.config.shouldQuitAfterLastWindowClosed
|
||||
}
|
||||
|
||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||
@ -242,7 +242,7 @@ class AppDelegate: NSObject,
|
||||
|
||||
/// Sync all of our menu item keyboard shortcuts with the Ghostty configuration.
|
||||
private func syncMenuShortcuts() {
|
||||
guard ghostty.config != nil else { return }
|
||||
guard ghostty.readiness == .ready else { return }
|
||||
|
||||
syncMenuShortcut(action: "open_config", menuItem: self.menuOpenConfig)
|
||||
syncMenuShortcut(action: "reload_config", menuItem: self.menuReloadConfig)
|
||||
@ -286,19 +286,16 @@ class AppDelegate: NSObject,
|
||||
/// Syncs a single menu shortcut for the given action. The action string is the same
|
||||
/// action string used for the Ghostty configuration.
|
||||
private func syncMenuShortcut(action: String, menuItem: NSMenuItem?) {
|
||||
guard let cfg = ghostty.config else { return }
|
||||
guard let menu = menuItem else { return }
|
||||
|
||||
let trigger = ghostty_config_trigger(cfg, action, UInt(action.count))
|
||||
guard let equiv = Ghostty.keyEquivalent(key: trigger.key) else {
|
||||
guard let equiv = ghostty.config.keyEquivalent(for: action) else {
|
||||
// No shortcut, clear the menu item
|
||||
menu.keyEquivalent = ""
|
||||
menu.keyEquivalentModifierMask = []
|
||||
return
|
||||
}
|
||||
|
||||
menu.keyEquivalent = equiv
|
||||
menu.keyEquivalentModifierMask = Ghostty.eventModifierFlags(mods: trigger.mods)
|
||||
menu.keyEquivalent = equiv.key
|
||||
menu.keyEquivalentModifierMask = equiv.modifiers
|
||||
}
|
||||
|
||||
private func focusedSurface() -> ghostty_surface_t? {
|
||||
@ -341,7 +338,7 @@ class AppDelegate: NSObject,
|
||||
withCompletionHandler(options)
|
||||
}
|
||||
|
||||
//MARK: - GhosttyAppStateDelegate
|
||||
//MARK: - GhosttyAppDelegate
|
||||
|
||||
func findSurface(forUUID uuid: UUID) -> Ghostty.SurfaceView? {
|
||||
for c in terminalManager.windows {
|
||||
@ -353,11 +350,11 @@ class AppDelegate: NSObject,
|
||||
return nil
|
||||
}
|
||||
|
||||
func configDidReload(_ state: Ghostty.AppState) {
|
||||
func configDidReload(_ state: Ghostty.App) {
|
||||
// Depending on the "window-save-state" setting we have to set the NSQuitAlwaysKeepsWindows
|
||||
// configuration. This is the only way to carefully control whether macOS invokes the
|
||||
// state restoration system.
|
||||
switch (ghostty.windowSaveState) {
|
||||
switch (ghostty.config.windowSaveState) {
|
||||
case "never": UserDefaults.standard.setValue(false, forKey: "NSQuitAlwaysKeepsWindows")
|
||||
case "always": UserDefaults.standard.setValue(true, forKey: "NSQuitAlwaysKeepsWindows")
|
||||
case "default": fallthrough
|
||||
@ -373,7 +370,7 @@ class AppDelegate: NSObject,
|
||||
|
||||
// If we have configuration errors, we need to show them.
|
||||
let c = ConfigurationErrorsController.sharedInstance
|
||||
c.errors = state.configErrors()
|
||||
c.errors = state.config.errors
|
||||
if (c.errors.count > 0) {
|
||||
if (c.window == nil || !c.window!.isVisible) {
|
||||
c.showWindow(self)
|
||||
@ -383,7 +380,7 @@ class AppDelegate: NSObject,
|
||||
|
||||
/// Sync the appearance of our app with the theme specified in the config.
|
||||
private func syncAppearance() {
|
||||
guard let theme = ghostty.windowTheme else { return }
|
||||
guard let theme = ghostty.config.windowTheme else { return }
|
||||
switch (theme) {
|
||||
case "dark":
|
||||
let appearance = NSAppearance(named: .darkAqua)
|
@ -11,7 +11,7 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
override var windowNibName: NSNib.Name? { "Terminal" }
|
||||
|
||||
/// The app instance that this terminal view will represent.
|
||||
let ghostty: Ghostty.AppState
|
||||
let ghostty: Ghostty.App
|
||||
|
||||
/// The currently focused surface.
|
||||
var focusedSurface: Ghostty.SurfaceView? = nil
|
||||
@ -46,7 +46,7 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
/// changes in the list.
|
||||
private var tabWindowsHash: Int = 0
|
||||
|
||||
init(_ ghostty: Ghostty.AppState,
|
||||
init(_ ghostty: Ghostty.App,
|
||||
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
||||
withSurfaceTree tree: Ghostty.SplitNode? = nil
|
||||
) {
|
||||
@ -101,7 +101,6 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
tabListenForFrame = false
|
||||
|
||||
guard let windows = self.window?.tabbedWindows else { return }
|
||||
guard let cfg = ghostty.config else { return }
|
||||
|
||||
// We only listen for frame changes if we have more than 1 window,
|
||||
// otherwise the accessory view doesn't matter.
|
||||
@ -109,8 +108,7 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
|
||||
for (index, window) in windows.enumerated().prefix(9) {
|
||||
let action = "goto_tab:\(index + 1)"
|
||||
let trigger = ghostty_config_trigger(cfg, action, UInt(action.count))
|
||||
guard let equiv = Ghostty.keyEquivalentLabel(key: trigger.key, mods: trigger.mods) else {
|
||||
guard let equiv = ghostty.config.keyEquivalent(for: action) else {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -157,13 +155,13 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
window.identifier = .init(String(describing: TerminalWindowRestoration.self))
|
||||
|
||||
// If window decorations are disabled, remove our title
|
||||
if (!ghostty.windowDecorations) { window.styleMask.remove(.titled) }
|
||||
if (!ghostty.config.windowDecorations) { window.styleMask.remove(.titled) }
|
||||
|
||||
// Terminals typically operate in sRGB color space and macOS defaults
|
||||
// to "native" which is typically P3. There is a lot more resources
|
||||
// covered in thie GitHub issue: https://github.com/mitchellh/ghostty/pull/376
|
||||
// Ghostty defaults to sRGB but this can be overridden.
|
||||
switch (ghostty.windowColorspace) {
|
||||
switch (ghostty.config.windowColorspace) {
|
||||
case "display-p3":
|
||||
window.colorSpace = .displayP3
|
||||
case "srgb":
|
||||
@ -462,7 +460,7 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
}
|
||||
|
||||
func cellSizeDidChange(to: NSSize) {
|
||||
guard ghostty.windowStepResize else { return }
|
||||
guard ghostty.config.windowStepResize else { return }
|
||||
self.window?.contentResizeIncrements = to
|
||||
}
|
||||
|
||||
@ -504,7 +502,7 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
str = cc.contents
|
||||
}
|
||||
|
||||
Ghostty.AppState.completeClipboardRequest(cc.surface, data: str, state: cc.state, confirmed: true)
|
||||
Ghostty.App.completeClipboardRequest(cc.surface, data: str, state: cc.state, confirmed: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -591,7 +589,7 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
||||
// If we already have a clipboard confirmation view up, we ignore this request.
|
||||
// This shouldn't be possible...
|
||||
guard self.clipboardConfirmation == nil else {
|
||||
Ghostty.AppState.completeClipboardRequest(surface, data: "", state: state, confirmed: true)
|
||||
Ghostty.App.completeClipboardRequest(surface, data: "", state: state, confirmed: true)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ class TerminalManager {
|
||||
let closePublisher: AnyCancellable
|
||||
}
|
||||
|
||||
let ghostty: Ghostty.AppState
|
||||
let ghostty: Ghostty.App
|
||||
|
||||
/// The currently focused surface of the main window.
|
||||
var focusedSurface: Ghostty.SurfaceView? { mainWindow?.controller.focusedSurface }
|
||||
@ -37,7 +37,7 @@ class TerminalManager {
|
||||
return windows.last
|
||||
}
|
||||
|
||||
init(_ ghostty: Ghostty.AppState) {
|
||||
init(_ ghostty: Ghostty.App) {
|
||||
self.ghostty = ghostty
|
||||
|
||||
let center = NotificationCenter.default
|
||||
@ -66,7 +66,7 @@ class TerminalManager {
|
||||
let window = c.window!
|
||||
|
||||
// We want to go fullscreen if we're configured for new windows to go fullscreen
|
||||
var toggleFullScreen = ghostty.windowFullscreen
|
||||
var toggleFullScreen = ghostty.config.windowFullscreen
|
||||
|
||||
// If the previous focused window prior to creating this window is fullscreen,
|
||||
// then this window also becomes fullscreen.
|
||||
@ -130,7 +130,7 @@ class TerminalManager {
|
||||
controller.showWindow(self)
|
||||
|
||||
// Add the window to the tab group and show it.
|
||||
switch ghostty.windowNewTabPosition {
|
||||
switch ghostty.config.windowNewTabPosition {
|
||||
case "end":
|
||||
// If we already have a tab group and we want the new tab to open at the end,
|
||||
// then we use the last window in the tab group as the parent.
|
||||
|
@ -66,7 +66,7 @@ class TerminalWindowRestoration: NSObject, NSWindowRestoration {
|
||||
|
||||
// If our configuration is "never" then we never restore the state
|
||||
// no matter what.
|
||||
if (appDelegate.terminalManager.ghostty.windowSaveState == "never") {
|
||||
if (appDelegate.terminalManager.ghostty.config.windowSaveState == "never") {
|
||||
completionHandler(nil, nil)
|
||||
return
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ protocol TerminalViewModel: ObservableObject {
|
||||
|
||||
/// The main terminal view. This terminal view supports splits.
|
||||
struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
@ObservedObject var ghostty: Ghostty.AppState
|
||||
@ObservedObject var ghostty: Ghostty.App
|
||||
|
||||
// The required view model
|
||||
@ObservedObject var viewModel: ViewModel
|
||||
@ -83,7 +83,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
VStack(spacing: 0) {
|
||||
// If we're running in debug mode we show a warning so that users
|
||||
// know that performance will be degraded.
|
||||
if (ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG) {
|
||||
if (Ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG) {
|
||||
DebugBuildWarningView()
|
||||
}
|
||||
|
||||
|
@ -2,51 +2,36 @@ import SwiftUI
|
||||
import UserNotifications
|
||||
import GhosttyKit
|
||||
|
||||
protocol GhosttyAppStateDelegate: AnyObject {
|
||||
protocol GhosttyAppDelegate: AnyObject {
|
||||
/// Called when the configuration did finish reloading.
|
||||
func configDidReload(_ state: Ghostty.AppState)
|
||||
func configDidReload(_ app: Ghostty.App)
|
||||
|
||||
#if os(macOS)
|
||||
/// Called when a callback needs access to a specific surface. This should return nil
|
||||
/// when the surface is no longer valid.
|
||||
func findSurface(forUUID uuid: UUID) -> Ghostty.SurfaceView?
|
||||
#endif
|
||||
}
|
||||
|
||||
extension Ghostty {
|
||||
enum AppReadiness {
|
||||
case loading, error, ready
|
||||
}
|
||||
|
||||
enum FontSizeModification {
|
||||
case increase(Int)
|
||||
case decrease(Int)
|
||||
case reset
|
||||
}
|
||||
|
||||
struct Info {
|
||||
var mode: ghostty_build_mode_e
|
||||
var version: String
|
||||
}
|
||||
|
||||
/// The AppState is the global state that is associated with the Swift app. This handles initially
|
||||
/// initializing Ghostty, loading the configuration, etc.
|
||||
class AppState: ObservableObject {
|
||||
/// The readiness value of the state.
|
||||
@Published var readiness: AppReadiness = .loading
|
||||
|
||||
/// Optional delegate
|
||||
weak var delegate: GhosttyAppStateDelegate?
|
||||
|
||||
/// The ghostty global configuration. This should only be changed when it is definitely
|
||||
/// safe to change. It is definite safe to change only when the embedded app runtime
|
||||
/// in Ghostty says so (usually, only in a reload configuration callback).
|
||||
@Published var config: ghostty_config_t? = nil {
|
||||
didSet {
|
||||
// Free the old value whenever we change
|
||||
guard let old = oldValue else { return }
|
||||
ghostty_config_free(old)
|
||||
}
|
||||
// IMPORTANT: THIS IS NOT DONE.
|
||||
// This is a refactor/redo of Ghostty.AppState so that it supports both macOS and iOS
|
||||
class App: ObservableObject {
|
||||
enum Readiness: String {
|
||||
case loading, error, ready
|
||||
}
|
||||
|
||||
|
||||
/// Optional delegate
|
||||
weak var delegate: GhosttyAppDelegate?
|
||||
|
||||
/// The readiness value of the state.
|
||||
@Published var readiness: Readiness = .loading
|
||||
|
||||
/// The global app configuration. This defines the app level configuration plus any behavior
|
||||
/// for new windows, tabs, etc. Note that when creating a new window, it may inherit some
|
||||
/// configuration (i.e. font size) from the previously focused window. This would override this.
|
||||
private(set) var config: Config
|
||||
|
||||
/// The ghostty app instance. We only have one of these for the entire app, although I guess
|
||||
/// in theory you can have multiple... I don't know why you would...
|
||||
@Published var app: ghostty_app_t? = nil {
|
||||
@ -55,253 +40,118 @@ extension Ghostty {
|
||||
ghostty_app_free(old)
|
||||
}
|
||||
}
|
||||
|
||||
/// True if we should quit when the last window is closed.
|
||||
var shouldQuitAfterLastWindowClosed: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let key = "quit-after-last-window-closed"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v
|
||||
}
|
||||
|
||||
/// window-colorspace
|
||||
var windowColorspace: String {
|
||||
guard let config = self.config else { return "" }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-colorspace"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
/// window-save-state
|
||||
var windowSaveState: String {
|
||||
guard let config = self.config else { return "" }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-save-state"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
/// window-new-tab-position
|
||||
var windowNewTabPosition: String {
|
||||
guard let config = self.config else { return "" }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-new-tab-position"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
/// True if we need to confirm before quitting.
|
||||
var needsConfirmQuit: Bool {
|
||||
guard let app = app else { return false }
|
||||
return ghostty_app_needs_confirm_quit(app)
|
||||
}
|
||||
|
||||
/// Build information
|
||||
var info: Info {
|
||||
let raw = ghostty_info()
|
||||
let version = NSString(
|
||||
bytes: raw.version,
|
||||
length: Int(raw.version_len),
|
||||
encoding: NSUTF8StringEncoding
|
||||
) ?? "unknown"
|
||||
|
||||
return Info(mode: raw.build_mode, version: String(version))
|
||||
}
|
||||
|
||||
/// True if we want to render window decorations
|
||||
var windowDecorations: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let key = "window-decoration"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v;
|
||||
}
|
||||
|
||||
/// The window theme as a string.
|
||||
var windowTheme: String? {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-theme"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
|
||||
guard let ptr = v else { return nil }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
/// Whether to resize windows in discrete steps or use "fluid" resizing
|
||||
var windowStepResize: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false
|
||||
let key = "window-step-resize"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v
|
||||
}
|
||||
|
||||
/// Whether to open new windows in fullscreen.
|
||||
var windowFullscreen: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false
|
||||
let key = "fullscreen"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v
|
||||
}
|
||||
|
||||
/// The background opacity.
|
||||
var backgroundOpacity: Double {
|
||||
guard let config = self.config else { return 1 }
|
||||
var v: Double = 1
|
||||
let key = "background-opacity"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v;
|
||||
}
|
||||
|
||||
init() {
|
||||
// Initialize ghostty global state. This happens once per process.
|
||||
guard ghostty_init() == GHOSTTY_SUCCESS else {
|
||||
AppDelegate.logger.critical("ghostty_init failed")
|
||||
if ghostty_init() != GHOSTTY_SUCCESS {
|
||||
logger.critical("ghostty_init failed, weird things may happen")
|
||||
readiness = .error
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the global configuration.
|
||||
guard let cfg = Self.loadConfig() else {
|
||||
self.config = Config()
|
||||
if self.config.config == nil {
|
||||
readiness = .error
|
||||
return
|
||||
}
|
||||
self.config = cfg;
|
||||
|
||||
|
||||
// Create our "runtime" config. The "runtime" is the configuration that ghostty
|
||||
// uses to interface with the application runtime environment.
|
||||
var runtime_cfg = ghostty_runtime_config_s(
|
||||
userdata: Unmanaged.passUnretained(self).toOpaque(),
|
||||
supports_selection_clipboard: false,
|
||||
wakeup_cb: { userdata in AppState.wakeup(userdata) },
|
||||
reload_config_cb: { userdata in AppState.reloadConfig(userdata) },
|
||||
open_config_cb: { userdata in AppState.openConfig(userdata) },
|
||||
set_title_cb: { userdata, title in AppState.setTitle(userdata, title: title) },
|
||||
set_mouse_shape_cb: { userdata, shape in AppState.setMouseShape(userdata, shape: shape) },
|
||||
set_mouse_visibility_cb: { userdata, visible in AppState.setMouseVisibility(userdata, visible: visible) },
|
||||
read_clipboard_cb: { userdata, loc, state in AppState.readClipboard(userdata, location: loc, state: state) },
|
||||
confirm_read_clipboard_cb: { userdata, str, state, request in AppState.confirmReadClipboard(userdata, string: str, state: state, request: request ) },
|
||||
write_clipboard_cb: { userdata, str, loc, confirm in AppState.writeClipboard(userdata, string: str, location: loc, confirm: confirm) },
|
||||
new_split_cb: { userdata, direction, surfaceConfig in AppState.newSplit(userdata, direction: direction, config: surfaceConfig) },
|
||||
new_tab_cb: { userdata, surfaceConfig in AppState.newTab(userdata, config: surfaceConfig) },
|
||||
new_window_cb: { userdata, surfaceConfig in AppState.newWindow(userdata, config: surfaceConfig) },
|
||||
control_inspector_cb: { userdata, mode in AppState.controlInspector(userdata, mode: mode) },
|
||||
close_surface_cb: { userdata, processAlive in AppState.closeSurface(userdata, processAlive: processAlive) },
|
||||
focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) },
|
||||
wakeup_cb: { userdata in App.wakeup(userdata) },
|
||||
reload_config_cb: { userdata in App.reloadConfig(userdata) },
|
||||
open_config_cb: { userdata in App.openConfig(userdata) },
|
||||
set_title_cb: { userdata, title in App.setTitle(userdata, title: title) },
|
||||
set_mouse_shape_cb: { userdata, shape in App.setMouseShape(userdata, shape: shape) },
|
||||
set_mouse_visibility_cb: { userdata, visible in App.setMouseVisibility(userdata, visible: visible) },
|
||||
read_clipboard_cb: { userdata, loc, state in App.readClipboard(userdata, location: loc, state: state) },
|
||||
confirm_read_clipboard_cb: { userdata, str, state, request in App.confirmReadClipboard(userdata, string: str, state: state, request: request ) },
|
||||
write_clipboard_cb: { userdata, str, loc, confirm in App.writeClipboard(userdata, string: str, location: loc, confirm: confirm) },
|
||||
new_split_cb: { userdata, direction, surfaceConfig in App.newSplit(userdata, direction: direction, config: surfaceConfig) },
|
||||
new_tab_cb: { userdata, surfaceConfig in App.newTab(userdata, config: surfaceConfig) },
|
||||
new_window_cb: { userdata, surfaceConfig in App.newWindow(userdata, config: surfaceConfig) },
|
||||
control_inspector_cb: { userdata, mode in App.controlInspector(userdata, mode: mode) },
|
||||
close_surface_cb: { userdata, processAlive in App.closeSurface(userdata, processAlive: processAlive) },
|
||||
focus_split_cb: { userdata, direction in App.focusSplit(userdata, direction: direction) },
|
||||
resize_split_cb: { userdata, direction, amount in
|
||||
AppState.resizeSplit(userdata, direction: direction, amount: amount) },
|
||||
App.resizeSplit(userdata, direction: direction, amount: amount) },
|
||||
equalize_splits_cb: { userdata in
|
||||
AppState.equalizeSplits(userdata) },
|
||||
toggle_split_zoom_cb: { userdata in AppState.toggleSplitZoom(userdata) },
|
||||
goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) },
|
||||
toggle_fullscreen_cb: { userdata, nonNativeFullscreen in AppState.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) },
|
||||
set_initial_window_size_cb: { userdata, width, height in AppState.setInitialWindowSize(userdata, width: width, height: height) },
|
||||
render_inspector_cb: { userdata in AppState.renderInspector(userdata) },
|
||||
set_cell_size_cb: { userdata, width, height in AppState.setCellSize(userdata, width: width, height: height) },
|
||||
App.equalizeSplits(userdata) },
|
||||
toggle_split_zoom_cb: { userdata in App.toggleSplitZoom(userdata) },
|
||||
goto_tab_cb: { userdata, n in App.gotoTab(userdata, n: n) },
|
||||
toggle_fullscreen_cb: { userdata, nonNativeFullscreen in App.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) },
|
||||
set_initial_window_size_cb: { userdata, width, height in App.setInitialWindowSize(userdata, width: width, height: height) },
|
||||
render_inspector_cb: { userdata in App.renderInspector(userdata) },
|
||||
set_cell_size_cb: { userdata, width, height in App.setCellSize(userdata, width: width, height: height) },
|
||||
show_desktop_notification_cb: { userdata, title, body in
|
||||
AppState.showUserNotification(userdata, title: title, body: body)
|
||||
App.showUserNotification(userdata, title: title, body: body)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
// Create the ghostty app.
|
||||
guard let app = ghostty_app_new(&runtime_cfg, cfg) else {
|
||||
AppDelegate.logger.critical("ghostty_app_new failed")
|
||||
guard let app = ghostty_app_new(&runtime_cfg, config.config) else {
|
||||
logger.critical("ghostty_app_new failed")
|
||||
readiness = .error
|
||||
return
|
||||
}
|
||||
self.app = app
|
||||
|
||||
|
||||
#if os(macOS)
|
||||
// Subscribe to notifications for keyboard layout change so that we can update Ghostty.
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(self.keyboardSelectionDidChange(notification:)),
|
||||
name: NSTextInputContext.keyboardSelectionDidChangeNotification,
|
||||
object: nil)
|
||||
|
||||
#endif
|
||||
|
||||
self.readiness = .ready
|
||||
}
|
||||
|
||||
|
||||
deinit {
|
||||
// This will force the didSet callbacks to run which free.
|
||||
self.app = nil
|
||||
self.config = nil
|
||||
|
||||
|
||||
#if os(macOS)
|
||||
// Remove our observer
|
||||
NotificationCenter.default.removeObserver(
|
||||
self,
|
||||
name: NSTextInputContext.keyboardSelectionDidChangeNotification,
|
||||
object: nil)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Initializes a new configuration and loads all the values.
|
||||
static func loadConfig() -> ghostty_config_t? {
|
||||
// Initialize the global configuration.
|
||||
guard let cfg = ghostty_config_new() else {
|
||||
AppDelegate.logger.critical("ghostty_config_new failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load our configuration files from the home directory.
|
||||
ghostty_config_load_default_files(cfg);
|
||||
ghostty_config_load_cli_args(cfg);
|
||||
ghostty_config_load_recursive_files(cfg);
|
||||
|
||||
// TODO: we'd probably do some config loading here... for now we'd
|
||||
// have to do this synchronously. When we support config updating we can do
|
||||
// this async and update later.
|
||||
|
||||
// Finalize will make our defaults available.
|
||||
ghostty_config_finalize(cfg)
|
||||
|
||||
// Log any configuration errors. These will be automatically shown in a
|
||||
// pop-up window too.
|
||||
let errCount = ghostty_config_errors_count(cfg)
|
||||
if errCount > 0 {
|
||||
AppDelegate.logger.warning("config error: \(errCount) configuration errors on reload")
|
||||
var errors: [String] = [];
|
||||
for i in 0..<errCount {
|
||||
let err = ghostty_config_get_error(cfg, UInt32(i))
|
||||
let message = String(cString: err.message)
|
||||
errors.append(message)
|
||||
AppDelegate.logger.warning("config error: \(message)")
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
/// Returns the configuration errors (if any).
|
||||
func configErrors() -> [String] {
|
||||
guard let cfg = self.config else { return [] }
|
||||
|
||||
var errors: [String] = [];
|
||||
let errCount = ghostty_config_errors_count(cfg)
|
||||
for i in 0..<errCount {
|
||||
let err = ghostty_config_get_error(cfg, UInt32(i))
|
||||
let message = String(cString: err.message)
|
||||
errors.append(message)
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
|
||||
// MARK: App Operations
|
||||
|
||||
func appTick() {
|
||||
guard let app = self.app else { return }
|
||||
|
||||
// Tick our app, which lets us know if we want to quit
|
||||
let exit = ghostty_app_tick(app)
|
||||
if (!exit) { return }
|
||||
|
||||
// On iOS, applications do not terminate programmatically like they do
|
||||
// on macOS. On iOS, applications are only terminated when a user physically
|
||||
// closes the application (i.e. going to the home screen). If we request
|
||||
// exit on iOS we ignore it.
|
||||
#if os(iOS)
|
||||
logger.info("quit request received, ignoring on iOS")
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
// We want to quit, start that process
|
||||
NSApplication.shared.terminate(nil)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func openConfig() {
|
||||
guard let app = self.app else { return }
|
||||
ghostty_app_open_config(app)
|
||||
@ -311,7 +161,7 @@ extension Ghostty {
|
||||
guard let app = self.app else { return }
|
||||
ghostty_app_reload_config(app)
|
||||
}
|
||||
|
||||
|
||||
/// Request that the given surface is closed. This will trigger the full normal surface close event
|
||||
/// cycle which will call our close surface callback.
|
||||
func requestClose(surface: ghostty_surface_t) {
|
||||
@ -321,14 +171,14 @@ extension Ghostty {
|
||||
func newTab(surface: ghostty_surface_t) {
|
||||
let action = "new_tab"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
func newWindow(surface: ghostty_surface_t) {
|
||||
let action = "new_window"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,16 +201,22 @@ extension Ghostty {
|
||||
func splitToggleZoom(surface: ghostty_surface_t) {
|
||||
let action = "toggle_split_zoom"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
func toggleFullscreen(surface: ghostty_surface_t) {
|
||||
let action = "toggle_fullscreen"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
enum FontSizeModification {
|
||||
case increase(Int)
|
||||
case decrease(Int)
|
||||
case reset
|
||||
}
|
||||
|
||||
func changeFontSize(surface: ghostty_surface_t, _ change: FontSizeModification) {
|
||||
let action: String
|
||||
@ -373,25 +229,80 @@ extension Ghostty {
|
||||
action = "reset_font_size"
|
||||
}
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
func toggleTerminalInspector(surface: ghostty_surface_t) {
|
||||
let action = "inspector:toggle"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if os(iOS)
|
||||
// MARK: Ghostty Callbacks (iOS)
|
||||
|
||||
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {}
|
||||
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? { return nil }
|
||||
static func openConfig(_ userdata: UnsafeMutableRawPointer?) {}
|
||||
static func setTitle(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?) {}
|
||||
static func setMouseShape(_ userdata: UnsafeMutableRawPointer?, shape: ghostty_mouse_shape_e) {}
|
||||
static func setMouseVisibility(_ userdata: UnsafeMutableRawPointer?, visible: Bool) {}
|
||||
static func readClipboard(
|
||||
_ userdata: UnsafeMutableRawPointer?,
|
||||
location: ghostty_clipboard_e,
|
||||
state: UnsafeMutableRawPointer?
|
||||
) {}
|
||||
|
||||
static func confirmReadClipboard(
|
||||
_ userdata: UnsafeMutableRawPointer?,
|
||||
string: UnsafePointer<CChar>?,
|
||||
state: UnsafeMutableRawPointer?,
|
||||
request: ghostty_clipboard_request_e
|
||||
) {}
|
||||
|
||||
static func writeClipboard(
|
||||
_ userdata: UnsafeMutableRawPointer?,
|
||||
string: UnsafePointer<CChar>?,
|
||||
location: ghostty_clipboard_e,
|
||||
confirm: Bool
|
||||
) {}
|
||||
|
||||
static func newSplit(
|
||||
_ userdata: UnsafeMutableRawPointer?,
|
||||
direction: ghostty_split_direction_e,
|
||||
config: ghostty_surface_config_s
|
||||
) {}
|
||||
|
||||
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {}
|
||||
static func newWindow(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {}
|
||||
static func controlInspector(_ userdata: UnsafeMutableRawPointer?, mode: ghostty_inspector_mode_e) {}
|
||||
static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {}
|
||||
static func focusSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_focus_direction_e) {}
|
||||
static func resizeSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_resize_direction_e, amount: UInt16) {}
|
||||
static func equalizeSplits(_ userdata: UnsafeMutableRawPointer?) {}
|
||||
static func toggleSplitZoom(_ userdata: UnsafeMutableRawPointer?) {}
|
||||
static func gotoTab(_ userdata: UnsafeMutableRawPointer?, n: Int32) {}
|
||||
static func toggleFullscreen(_ userdata: UnsafeMutableRawPointer?, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {}
|
||||
static func setInitialWindowSize(_ userdata: UnsafeMutableRawPointer?, width: UInt32, height: UInt32) {}
|
||||
static func renderInspector(_ userdata: UnsafeMutableRawPointer?) {}
|
||||
static func setCellSize(_ userdata: UnsafeMutableRawPointer?, width: UInt32, height: UInt32) {}
|
||||
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
// Called when the selected keyboard changes. We have to notify Ghostty so that
|
||||
// it can reload the keyboard mapping for input.
|
||||
@objc private func keyboardSelectionDidChange(notification: NSNotification) {
|
||||
guard let app = self.app else { return }
|
||||
ghostty_app_keyboard_changed(app)
|
||||
}
|
||||
|
||||
// MARK: Ghostty Callbacks
|
||||
|
||||
// MARK: Ghostty Callbacks (macOS)
|
||||
|
||||
static func newSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_direction_e, config: ghostty_surface_config_s) {
|
||||
let surface = self.surfaceUserdata(from: userdata)
|
||||
@ -534,14 +445,15 @@ extension Ghostty {
|
||||
}
|
||||
|
||||
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
|
||||
guard let newConfig = Self.loadConfig() else {
|
||||
let newConfig = Config()
|
||||
guard newConfig.loaded else {
|
||||
AppDelegate.logger.warning("failed to reload configuration")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Assign the new config. This will automatically free the old config.
|
||||
// It is safe to free the old config from within this function call.
|
||||
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
||||
let state = Unmanaged<Self>.fromOpaque(userdata!).takeUnretainedValue()
|
||||
state.config = newConfig
|
||||
|
||||
// If we have a delegate, notify.
|
||||
@ -549,11 +461,11 @@ extension Ghostty {
|
||||
delegate.configDidReload(state)
|
||||
}
|
||||
|
||||
return newConfig
|
||||
return newConfig.config
|
||||
}
|
||||
|
||||
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {
|
||||
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
||||
let state = Unmanaged<App>.fromOpaque(userdata!).takeUnretainedValue()
|
||||
|
||||
// Wakeup can be called from any thread so we schedule the app tick
|
||||
// from the main thread. There is probably some improvements we can make
|
||||
@ -662,7 +574,7 @@ extension Ghostty {
|
||||
let surface = self.surfaceUserdata(from: userdata)
|
||||
|
||||
guard let appState = self.appState(fromView: surface) else { return }
|
||||
guard appState.windowDecorations else {
|
||||
guard appState.config.windowDecorations else {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Tabs are disabled"
|
||||
alert.informativeText = "Enable window decorations to use tabs"
|
||||
@ -701,16 +613,18 @@ extension Ghostty {
|
||||
}
|
||||
|
||||
/// Returns the GhosttyState from the given userdata value.
|
||||
static private func appState(fromView view: SurfaceView) -> AppState? {
|
||||
static private func appState(fromView view: SurfaceView) -> App? {
|
||||
guard let surface = view.surface else { return nil }
|
||||
guard let app = ghostty_surface_app(surface) else { return nil }
|
||||
guard let app_ud = ghostty_app_userdata(app) else { return nil }
|
||||
return Unmanaged<AppState>.fromOpaque(app_ud).takeUnretainedValue()
|
||||
return Unmanaged<App>.fromOpaque(app_ud).takeUnretainedValue()
|
||||
}
|
||||
|
||||
/// Returns the surface view from the userdata.
|
||||
static private func surfaceUserdata(from userdata: UnsafeMutableRawPointer?) -> SurfaceView {
|
||||
return Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
238
macos/Sources/Ghostty/Ghostty.Config.swift
Normal file
238
macos/Sources/Ghostty/Ghostty.Config.swift
Normal file
@ -0,0 +1,238 @@
|
||||
import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
extension Ghostty {
|
||||
/// Maps to a `ghostty_config_t` and the various operations on that.
|
||||
class Config: ObservableObject {
|
||||
// The underlying C pointer to the Ghostty config structure. This
|
||||
// should never be accessed directly. Any operations on this should
|
||||
// be called from the functions on this or another class.
|
||||
private(set) var config: ghostty_config_t? = nil {
|
||||
didSet {
|
||||
// Free the old value whenever we change
|
||||
guard let old = oldValue else { return }
|
||||
ghostty_config_free(old)
|
||||
}
|
||||
}
|
||||
|
||||
/// True if the configuration is loaded
|
||||
var loaded: Bool { config != nil }
|
||||
|
||||
/// Return the errors found while loading the configuration.
|
||||
var errors: [String] {
|
||||
guard let cfg = self.config else { return [] }
|
||||
|
||||
var errors: [String] = [];
|
||||
let errCount = ghostty_config_errors_count(cfg)
|
||||
for i in 0..<errCount {
|
||||
let err = ghostty_config_get_error(cfg, UInt32(i))
|
||||
let message = String(cString: err.message)
|
||||
errors.append(message)
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
init() {
|
||||
if let cfg = Self.loadConfig() {
|
||||
self.config = cfg
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.config = nil
|
||||
}
|
||||
|
||||
/// Initializes a new configuration and loads all the values.
|
||||
static private func loadConfig() -> ghostty_config_t? {
|
||||
// Initialize the global configuration.
|
||||
guard let cfg = ghostty_config_new() else {
|
||||
logger.critical("ghostty_config_new failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load our configuration from files, CLI args, and then any referenced files.
|
||||
// We only do this on macOS because other Apple platforms do not have the
|
||||
// same filesystem concept.
|
||||
#if os(macOS)
|
||||
ghostty_config_load_default_files(cfg);
|
||||
ghostty_config_load_cli_args(cfg);
|
||||
ghostty_config_load_recursive_files(cfg);
|
||||
#endif
|
||||
|
||||
// TODO: we'd probably do some config loading here... for now we'd
|
||||
// have to do this synchronously. When we support config updating we can do
|
||||
// this async and update later.
|
||||
|
||||
// Finalize will make our defaults available.
|
||||
ghostty_config_finalize(cfg)
|
||||
|
||||
// Log any configuration errors. These will be automatically shown in a
|
||||
// pop-up window too.
|
||||
let errCount = ghostty_config_errors_count(cfg)
|
||||
if errCount > 0 {
|
||||
logger.warning("config error: \(errCount) configuration errors on reload")
|
||||
var errors: [String] = [];
|
||||
for i in 0..<errCount {
|
||||
let err = ghostty_config_get_error(cfg, UInt32(i))
|
||||
let message = String(cString: err.message)
|
||||
errors.append(message)
|
||||
logger.warning("config error: \(message)")
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
// MARK: - Keybindings
|
||||
|
||||
/// A convenience struct that has the key + modifiers for some keybinding.
|
||||
struct KeyEquivalent: CustomStringConvertible {
|
||||
let key: String
|
||||
let modifiers: NSEvent.ModifierFlags
|
||||
|
||||
var description: String {
|
||||
var key = self.key
|
||||
|
||||
// Note: the order below matters; it matches the ordering modifiers
|
||||
// shown for macOS menu shortcut labels.
|
||||
if modifiers.contains(.command) { key = "⌘\(key)" }
|
||||
if modifiers.contains(.shift) { key = "⇧\(key)" }
|
||||
if modifiers.contains(.option) { key = "⌥\(key)" }
|
||||
if modifiers.contains(.control) { key = "⌃\(key)" }
|
||||
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the key equivalent for the given action. The action is the name of the action
|
||||
/// in the Ghostty configuration. For example `keybind = cmd+q=quit` in Ghostty
|
||||
/// configuration would be "quit" action.
|
||||
///
|
||||
/// Returns nil if there is no key equivalent for the given action.
|
||||
func keyEquivalent(for action: String) -> KeyEquivalent? {
|
||||
guard let cfg = self.config else { return nil }
|
||||
|
||||
let trigger = ghostty_config_trigger(cfg, action, UInt(action.count))
|
||||
guard let equiv = Ghostty.keyEquivalent(key: trigger.key) else { return nil }
|
||||
|
||||
return KeyEquivalent(
|
||||
key: equiv,
|
||||
modifiers: Ghostty.eventModifierFlags(mods: trigger.mods)
|
||||
)
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Configuration Values
|
||||
|
||||
/// For all of the configuration values below, see the associated Ghostty documentation for
|
||||
/// details on what each means. We only add documentation if there is a strange conversion
|
||||
/// due to the embedded library and Swift.
|
||||
|
||||
var shouldQuitAfterLastWindowClosed: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let key = "quit-after-last-window-closed"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v
|
||||
}
|
||||
|
||||
var windowColorspace: String {
|
||||
guard let config = self.config else { return "" }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-colorspace"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
var windowSaveState: String {
|
||||
guard let config = self.config else { return "" }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-save-state"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
var windowNewTabPosition: String {
|
||||
guard let config = self.config else { return "" }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-new-tab-position"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
var windowDecorations: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let key = "window-decoration"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v;
|
||||
}
|
||||
|
||||
var windowTheme: String? {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-theme"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
|
||||
guard let ptr = v else { return nil }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
var windowStepResize: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false
|
||||
let key = "window-step-resize"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v
|
||||
}
|
||||
|
||||
var windowFullscreen: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false
|
||||
let key = "fullscreen"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v
|
||||
}
|
||||
|
||||
var backgroundOpacity: Double {
|
||||
guard let config = self.config else { return 1 }
|
||||
var v: Double = 1
|
||||
let key = "background-opacity"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v;
|
||||
}
|
||||
|
||||
var unfocusedSplitOpacity: Double {
|
||||
guard let config = self.config else { return 1 }
|
||||
var opacity: Double = 0.85
|
||||
let key = "unfocused-split-opacity"
|
||||
_ = ghostty_config_get(config, &opacity, key, UInt(key.count))
|
||||
return 1 - opacity
|
||||
}
|
||||
|
||||
var unfocusedSplitFill: Color {
|
||||
guard let config = self.config else { return .white }
|
||||
|
||||
var rgb: UInt32 = 16777215 // white default
|
||||
let key = "unfocused-split-fill"
|
||||
if (!ghostty_config_get(config, &rgb, key, UInt(key.count))) {
|
||||
let bg_key = "background"
|
||||
_ = ghostty_config_get(config, &rgb, bg_key, UInt(bg_key.count));
|
||||
}
|
||||
|
||||
let red = Double(rgb & 0xff)
|
||||
let green = Double((rgb >> 8) & 0xff)
|
||||
let blue = Double((rgb >> 16) & 0xff)
|
||||
|
||||
return Color(
|
||||
red: red / 255,
|
||||
green: green / 255,
|
||||
blue: blue / 255
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -7,21 +7,6 @@ extension Ghostty {
|
||||
return Self.keyToEquivalent[key]
|
||||
}
|
||||
|
||||
/// Returns the keyEquivalent label that includes the mods.
|
||||
static func keyEquivalentLabel(key: ghostty_input_key_e, mods: ghostty_input_mods_e) -> String? {
|
||||
guard var key = Self.keyEquivalent(key: key) else { return nil }
|
||||
let flags = Self.eventModifierFlags(mods: mods)
|
||||
|
||||
// Note: the order below matters; it matches the ordering modifiers show for
|
||||
// macOS menu shortcut labels.
|
||||
if flags.contains(.command) { key = "⌘\(key)" }
|
||||
if flags.contains(.shift) { key = "⇧\(key)" }
|
||||
if flags.contains(.option) { key = "⌥\(key)" }
|
||||
if flags.contains(.control) { key = "⌃\(key)" }
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
/// Returns the event modifier flags set for the Ghostty mods enum.
|
||||
static func eventModifierFlags(mods: ghostty_input_mods_e) -> NSEvent.ModifierFlags {
|
||||
var flags = NSEvent.ModifierFlags(rawValue: 0);
|
||||
|
@ -1,7 +1,14 @@
|
||||
import os
|
||||
import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
struct Ghostty {
|
||||
// The primary logger used by the GhosttyKit libraries.
|
||||
static let logger = Logger(
|
||||
subsystem: Bundle.main.bundleIdentifier!,
|
||||
category: "ghostty"
|
||||
)
|
||||
|
||||
// All the notifications that will be emitted will be put here.
|
||||
struct Notification {}
|
||||
|
||||
@ -12,6 +19,26 @@ struct Ghostty {
|
||||
static let userNotificationActionShow = "com.mitchellh.ghostty.userNotification.Show"
|
||||
}
|
||||
|
||||
// MARK: Build Info
|
||||
|
||||
extension Ghostty {
|
||||
struct Info {
|
||||
var mode: ghostty_build_mode_e
|
||||
var version: String
|
||||
}
|
||||
|
||||
static var info: Info {
|
||||
let raw = ghostty_info()
|
||||
let version = NSString(
|
||||
bytes: raw.version,
|
||||
length: Int(raw.version_len),
|
||||
encoding: NSUTF8StringEncoding
|
||||
) ?? "unknown"
|
||||
|
||||
return Info(mode: raw.build_mode, version: String(version))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Surface Notifications
|
||||
|
||||
extension Ghostty {
|
||||
|
@ -5,7 +5,7 @@ import GhosttyKit
|
||||
extension Ghostty {
|
||||
/// Render a terminal for the active app in the environment.
|
||||
struct Terminal: View {
|
||||
@EnvironmentObject private var ghostty: Ghostty.AppState
|
||||
@EnvironmentObject private var ghostty: Ghostty.App
|
||||
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String?
|
||||
|
||||
var body: some View {
|
||||
@ -49,40 +49,12 @@ extension Ghostty {
|
||||
// Maintain whether our window has focus (is key) or not
|
||||
@State private var windowFocus: Bool = true
|
||||
|
||||
@EnvironmentObject private var ghostty: Ghostty.AppState
|
||||
@EnvironmentObject private var ghostty: Ghostty.App
|
||||
|
||||
// This is true if the terminal is considered "focused". The terminal is focused if
|
||||
// it is both individually focused and the containing window is key.
|
||||
private var hasFocus: Bool { surfaceFocus && windowFocus }
|
||||
|
||||
// The opacity of the rectangle when unfocused.
|
||||
private var unfocusedOpacity: Double {
|
||||
var opacity: Double = 0.85
|
||||
let key = "unfocused-split-opacity"
|
||||
_ = ghostty_config_get(ghostty.config, &opacity, key, UInt(key.count))
|
||||
return 1 - opacity
|
||||
}
|
||||
|
||||
// The color for the rectangle overlay when unfocused.
|
||||
private var unfocusedFill: Color {
|
||||
var rgb: UInt32 = 16777215 // white default
|
||||
let key = "unfocused-split-fill"
|
||||
if (!ghostty_config_get(ghostty.config, &rgb, key, UInt(key.count))) {
|
||||
let bg_key = "background"
|
||||
_ = ghostty_config_get(ghostty.config, &rgb, bg_key, UInt(bg_key.count));
|
||||
}
|
||||
|
||||
let red = Double(rgb & 0xff)
|
||||
let green = Double((rgb >> 8) & 0xff)
|
||||
let blue = Double((rgb >> 16) & 0xff)
|
||||
|
||||
return Color(
|
||||
red: red / 255,
|
||||
green: green / 255,
|
||||
blue: blue / 255
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// We use a GeometryReader to get the frame bounds so that our metal surface
|
||||
@ -175,10 +147,10 @@ extension Ghostty {
|
||||
// because we want to keep our focused surface dark even if we don't have window
|
||||
// focus.
|
||||
if (isSplit && !surfaceFocus) {
|
||||
let overlayOpacity = unfocusedOpacity;
|
||||
let overlayOpacity = ghostty.config.unfocusedSplitOpacity;
|
||||
if (overlayOpacity > 0) {
|
||||
Rectangle()
|
||||
.fill(unfocusedFill)
|
||||
.fill(ghostty.config.unfocusedSplitFill)
|
||||
.allowsHitTesting(false)
|
||||
.opacity(overlayOpacity)
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
||||
# more details.
|
||||
"sha256-hE4MNVZx/kA90MPHEraJDayBtLw29HZfnFChLdXPS0g="
|
||||
"sha256-to0V9rCefIs8KcWsx+nopgQO4i7O3gb06LGNc6NXN2M="
|
||||
|
@ -45,6 +45,7 @@ const SDK = struct {
|
||||
|
||||
pub fn fromTarget(target: std.Target) !SDK {
|
||||
return switch (target.os.tag) {
|
||||
.ios => .{ .platform = "iPhoneOS", .version = "" },
|
||||
.macos => .{ .platform = "MacOSX", .version = "14" },
|
||||
else => {
|
||||
std.log.err("unsupported os={}", .{target.os.tag});
|
||||
|
@ -71,10 +71,12 @@ pub fn build(b: *std.Build) !void {
|
||||
.file = imgui.path("backends/imgui_impl_metal.mm"),
|
||||
.flags = flags.items,
|
||||
});
|
||||
lib.addCSourceFile(.{
|
||||
.file = imgui.path("backends/imgui_impl_osx.mm"),
|
||||
.flags = flags.items,
|
||||
});
|
||||
if (target.result.os.tag == .macos) {
|
||||
lib.addCSourceFile(.{
|
||||
.file = imgui.path("backends/imgui_impl_osx.mm"),
|
||||
.flags = flags.items,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lib.installHeadersDirectoryOptions(.{
|
||||
|
@ -15,6 +15,11 @@ pub fn build(b: *std.Build) !void {
|
||||
});
|
||||
lib.linkLibC();
|
||||
lib.addIncludePath(upstream.path("include"));
|
||||
if (target.result.isDarwin()) {
|
||||
const apple_sdk = @import("apple_sdk");
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
}
|
||||
|
||||
module.addIncludePath(upstream.path("include"));
|
||||
module.addIncludePath(.{ .path = "" });
|
||||
|
||||
@ -86,7 +91,7 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
b.installArtifact(lib);
|
||||
|
||||
{
|
||||
if (target.query.isNative()) {
|
||||
const test_exe = b.addTest(.{
|
||||
.name = "test",
|
||||
.root_source_file = .{ .path = "main.zig" },
|
||||
|
@ -1,12 +1,14 @@
|
||||
.{
|
||||
.name = "freetype",
|
||||
.version = "2.13.2",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
.freetype = .{
|
||||
.url = "https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-2.tar.gz",
|
||||
.hash = "1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d",
|
||||
},
|
||||
|
||||
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||
.libpng = .{ .path = "../libpng" },
|
||||
.zlib = .{ .path = "../zlib" },
|
||||
},
|
||||
|
@ -12,8 +12,15 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
module.addIncludePath(upstream.path(""));
|
||||
module.addIncludePath(.{ .path = "override" });
|
||||
if (target.result.isDarwin()) {
|
||||
// See pkg/harfbuzz/build.zig
|
||||
module.resolved_target = target;
|
||||
defer module.resolved_target = null;
|
||||
const apple_sdk = @import("apple_sdk");
|
||||
try apple_sdk.addPaths(b, module);
|
||||
}
|
||||
|
||||
{
|
||||
if (target.query.isNative()) {
|
||||
const test_exe = b.addTest(.{
|
||||
.name = "test",
|
||||
.root_source_file = .{ .path = "main.zig" },
|
||||
@ -45,6 +52,10 @@ fn buildGlslang(
|
||||
lib.linkLibCpp();
|
||||
lib.addIncludePath(upstream.path(""));
|
||||
lib.addIncludePath(.{ .path = "override" });
|
||||
if (target.result.isDarwin()) {
|
||||
const apple_sdk = @import("apple_sdk");
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
}
|
||||
|
||||
var flags = std.ArrayList([]const u8).init(b.allocator);
|
||||
defer flags.deinit();
|
||||
|
@ -7,5 +7,7 @@
|
||||
.url = "https://github.com/KhronosGroup/glslang/archive/refs/tags/13.1.1.tar.gz",
|
||||
.hash = "1220481fe19def1172cd0728743019c0f440181a6342b62d03e24d05c70141516799",
|
||||
},
|
||||
|
||||
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||
},
|
||||
}
|
||||
|
@ -34,6 +34,18 @@ pub fn build(b: *std.Build) !void {
|
||||
lib.addIncludePath(upstream.path("src"));
|
||||
module.addIncludePath(upstream.path("src"));
|
||||
|
||||
if (target.result.isDarwin()) {
|
||||
// This is definitely super sketchy and not right but without this
|
||||
// zig build test breaks on macOS. We have to look into what exactly
|
||||
// is going on here but this getting comitted in the interest of
|
||||
// unblocking zig build test.
|
||||
module.resolved_target = target;
|
||||
defer module.resolved_target = null;
|
||||
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
try apple_sdk.addPaths(b, module);
|
||||
}
|
||||
|
||||
const freetype_dep = b.dependency("freetype", .{ .target = target, .optimize = optimize });
|
||||
lib.linkLibrary(freetype_dep.artifact("freetype"));
|
||||
module.addIncludePath(freetype_dep.builder.dependency("freetype", .{}).path("include"));
|
||||
@ -59,19 +71,10 @@ pub fn build(b: *std.Build) !void {
|
||||
"-DHAVE_FT_DONE_MM_VAR=1",
|
||||
"-DHAVE_FT_GET_TRANSFORM=1",
|
||||
});
|
||||
if (coretext_enabled and target.result.isDarwin()) {
|
||||
// This is definitely super sketchy and not right but without this
|
||||
// zig build test breaks on macOS. We have to look into what exactly
|
||||
// is going on here but this getting comitted in the interest of
|
||||
// unblocking zig build test.
|
||||
module.resolved_target = target;
|
||||
defer module.resolved_target = null;
|
||||
|
||||
if (coretext_enabled) {
|
||||
try flags.appendSlice(&.{"-DHAVE_CORETEXT=1"});
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
try apple_sdk.addPaths(b, module);
|
||||
lib.linkFramework("ApplicationServices");
|
||||
module.linkFramework("ApplicationServices", .{});
|
||||
lib.linkFramework("CoreText");
|
||||
module.linkFramework("CoreText", .{});
|
||||
}
|
||||
|
||||
lib.addCSourceFile(.{
|
||||
|
@ -1,6 +1,7 @@
|
||||
.{
|
||||
.name = "harfbuzz",
|
||||
.version = "8.2.2",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
.harfbuzz = .{
|
||||
.url = "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/8.2.2.tar.gz",
|
||||
|
@ -15,6 +15,10 @@ pub fn build(b: *std.Build) !void {
|
||||
if (target.result.os.tag == .linux) {
|
||||
lib.linkSystemLibrary("m");
|
||||
}
|
||||
if (target.result.isDarwin()) {
|
||||
const apple_sdk = @import("apple_sdk");
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
}
|
||||
|
||||
const zlib_dep = b.dependency("zlib", .{ .target = target, .optimize = optimize });
|
||||
lib.linkLibrary(zlib_dep.artifact("z"));
|
||||
|
@ -1,14 +1,14 @@
|
||||
.{
|
||||
.name = "libpng",
|
||||
.version = "1.6.40",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
.libpng = .{
|
||||
.url = "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.40.tar.gz",
|
||||
.hash = "12203d2722e3af6f9556503b114c25fe3eead528a93f5f26eefcb187a460d1548e07",
|
||||
},
|
||||
|
||||
.zlib = .{
|
||||
.path = "../zlib",
|
||||
},
|
||||
.zlib = .{ .path = "../zlib" },
|
||||
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||
},
|
||||
}
|
||||
|
@ -28,14 +28,16 @@ pub fn build(b: *std.Build) !void {
|
||||
.file = .{ .path = "text/ext.c" },
|
||||
.flags = flags.items,
|
||||
});
|
||||
lib.linkFramework("Carbon");
|
||||
lib.linkFramework("CoreFoundation");
|
||||
lib.linkFramework("CoreGraphics");
|
||||
lib.linkFramework("CoreText");
|
||||
lib.linkFramework("CoreVideo");
|
||||
if (target.result.os.tag == .macos) {
|
||||
lib.linkFramework("Carbon");
|
||||
module.linkFramework("Carbon", .{});
|
||||
}
|
||||
|
||||
if (target.result.isDarwin()) {
|
||||
module.linkFramework("Carbon", .{});
|
||||
module.linkFramework("CoreFoundation", .{});
|
||||
module.linkFramework("CoreGraphics", .{});
|
||||
module.linkFramework("CoreText", .{});
|
||||
|
@ -12,7 +12,7 @@ pub fn build(b: *std.Build) !void {
|
||||
module.addIncludePath(upstream.path("src"));
|
||||
b.installArtifact(lib);
|
||||
|
||||
{
|
||||
if (target.query.isNative()) {
|
||||
const test_exe = b.addTest(.{
|
||||
.name = "test",
|
||||
.root_source_file = .{ .path = "main.zig" },
|
||||
@ -44,6 +44,11 @@ fn buildOniguruma(
|
||||
lib.linkLibC();
|
||||
lib.addIncludePath(upstream.path("src"));
|
||||
|
||||
if (target.result.isDarwin()) {
|
||||
const apple_sdk = @import("apple_sdk");
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
}
|
||||
|
||||
lib.addConfigHeader(b.addConfigHeader(.{
|
||||
.style = .{ .cmake = upstream.path("src/config.h.cmake.in") },
|
||||
}, .{
|
||||
|
@ -7,5 +7,7 @@
|
||||
.url = "https://github.com/kkos/oniguruma/archive/refs/tags/v6.9.9.tar.gz",
|
||||
.hash = "1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb",
|
||||
},
|
||||
|
||||
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||
},
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ pub fn build(b: *std.Build) !void {
|
||||
if (target.result.os.tag != .windows) {
|
||||
lib.linkSystemLibrary("pthread");
|
||||
}
|
||||
if (target.result.isDarwin()) {
|
||||
const apple_sdk = @import("apple_sdk");
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
}
|
||||
|
||||
lib.addIncludePath(upstream.path(""));
|
||||
lib.addIncludePath(.{ .path = "" });
|
||||
@ -68,7 +72,7 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
b.installArtifact(lib);
|
||||
|
||||
{
|
||||
if (target.query.isNative()) {
|
||||
const test_exe = b.addTest(.{
|
||||
.name = "test",
|
||||
.root_source_file = .{ .path = "main.zig" },
|
||||
|
@ -1,10 +1,13 @@
|
||||
.{
|
||||
.name = "pixman",
|
||||
.version = "0.42.2",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
.pixman = .{
|
||||
.url = "https://deps.files.ghostty.dev/pixman-pixman-0.42.2.tar.gz",
|
||||
.hash = "12209b9206f9a5d31ccd9a2312cc72cb9dfc3e034aee1883c549dc1d753fae457230",
|
||||
},
|
||||
|
||||
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||
},
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ pub fn build(b: *std.Build) !void {
|
||||
const lib = try buildSpirvCross(b, upstream, target, optimize);
|
||||
b.installArtifact(lib);
|
||||
|
||||
{
|
||||
if (target.query.isNative()) {
|
||||
const test_exe = b.addTest(.{
|
||||
.name = "test",
|
||||
.root_source_file = .{ .path = "main.zig" },
|
||||
@ -42,8 +42,10 @@ fn buildSpirvCross(
|
||||
});
|
||||
lib.linkLibC();
|
||||
lib.linkLibCpp();
|
||||
//lib.addIncludePath(upstream.path(""));
|
||||
//lib.addIncludePath(.{ .path = "override" });
|
||||
if (target.result.isDarwin()) {
|
||||
const apple_sdk = @import("apple_sdk");
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
}
|
||||
|
||||
var flags = std.ArrayList([]const u8).init(b.allocator);
|
||||
defer flags.deinit();
|
||||
|
@ -7,5 +7,7 @@
|
||||
.url = "https://github.com/KhronosGroup/SPIRV-Cross/archive/4818f7e7ef7b7078a3a7a5a52c4a338e0dda22f4.tar.gz",
|
||||
.hash = "1220b2d8a6cff1926ef28a29e312a0a503b555ebc2f082230b882410f49e672ac9c6",
|
||||
},
|
||||
|
||||
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||
},
|
||||
}
|
||||
|
@ -13,6 +13,11 @@ pub fn build(b: *std.Build) !void {
|
||||
});
|
||||
lib.linkLibC();
|
||||
lib.addIncludePath(upstream.path(""));
|
||||
if (target.result.isDarwin()) {
|
||||
const apple_sdk = @import("apple_sdk");
|
||||
try apple_sdk.addPaths(b, &lib.root_module);
|
||||
}
|
||||
|
||||
lib.installHeadersDirectoryOptions(.{
|
||||
.source_dir = upstream.path(""),
|
||||
.install_dir = .header,
|
||||
|
@ -1,10 +1,13 @@
|
||||
.{
|
||||
.name = "zlib",
|
||||
.version = "1.3.0",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
.zlib = .{
|
||||
.url = "https://github.com/madler/zlib/archive/refs/tags/v1.3.tar.gz",
|
||||
.hash = "12207d353609d95cee9da7891919e6d9582e97b7aa2831bd50f33bf523a582a08547",
|
||||
},
|
||||
|
||||
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||
},
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ fn setupFd(src: File.Handle, target: i32) !void {
|
||||
}
|
||||
}
|
||||
},
|
||||
.macos => {
|
||||
.ios, .macos => {
|
||||
// Mac doesn't support dup3 so we use dup2. We purposely clear
|
||||
// CLO_ON_EXEC for this fd.
|
||||
const flags = try os.fcntl(src, os.F.GETFD, 0);
|
||||
|
@ -1664,6 +1664,9 @@ pub const CAPI = struct {
|
||||
ptr: *Surface,
|
||||
window: *anyopaque,
|
||||
) void {
|
||||
// This is only supported on macOS
|
||||
if (comptime builtin.target.os.tag != .macos) return;
|
||||
|
||||
const config = ptr.app.config;
|
||||
|
||||
// Do nothing if we don't have background transparency enabled
|
||||
|
@ -15,6 +15,12 @@ pub const Options = struct {
|
||||
/// The path to write the framework
|
||||
out_path: []const u8,
|
||||
|
||||
/// The libraries to bundle
|
||||
libraries: []const Library,
|
||||
};
|
||||
|
||||
/// A single library to bundle into the xcframework.
|
||||
pub const Library = struct {
|
||||
/// Library file (dylib, a) to package.
|
||||
library: LazyPath,
|
||||
|
||||
@ -41,10 +47,12 @@ pub fn create(b: *std.Build, opts: Options) *XCFrameworkStep {
|
||||
const run = RunStep.create(b, b.fmt("xcframework {s}", .{opts.name}));
|
||||
run.has_side_effects = true;
|
||||
run.addArgs(&.{ "xcodebuild", "-create-xcframework" });
|
||||
run.addArg("-library");
|
||||
run.addFileArg(opts.library);
|
||||
run.addArg("-headers");
|
||||
run.addFileArg(opts.headers);
|
||||
for (opts.libraries) |lib| {
|
||||
run.addArg("-library");
|
||||
run.addFileArg(lib.library);
|
||||
run.addArg("-headers");
|
||||
run.addFileArg(lib.headers);
|
||||
}
|
||||
run.addArg("-output");
|
||||
run.addArg(opts.out_path);
|
||||
break :run run;
|
||||
|
@ -18,7 +18,7 @@ pub const SplitResizeDirection = Binding.Action.SplitResizeDirection;
|
||||
// in theory for XKB too on Linux but we don't need it right now.
|
||||
pub const Keymap = switch (builtin.os.tag) {
|
||||
.macos => @import("input/KeymapDarwin.zig"),
|
||||
else => struct {},
|
||||
else => @import("input/KeymapNoop.zig"),
|
||||
};
|
||||
|
||||
test {
|
||||
|
38
src/input/KeymapNoop.zig
Normal file
38
src/input/KeymapNoop.zig
Normal file
@ -0,0 +1,38 @@
|
||||
//! A noop implementation of the keymap interface so that the embedded
|
||||
//! library can compile on non-macOS platforms.
|
||||
const KeymapNoop = @This();
|
||||
|
||||
const Mods = @import("key.zig").Mods;
|
||||
|
||||
pub const State = struct {};
|
||||
pub const Translation = struct {
|
||||
text: []const u8,
|
||||
composing: bool,
|
||||
};
|
||||
|
||||
pub fn init() !KeymapNoop {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const KeymapNoop) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn reload(self: *KeymapNoop) !void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn translate(
|
||||
self: *const KeymapNoop,
|
||||
out: []u8,
|
||||
state: *State,
|
||||
code: u16,
|
||||
mods: Mods,
|
||||
) !Translation {
|
||||
_ = self;
|
||||
_ = out;
|
||||
_ = state;
|
||||
_ = code;
|
||||
_ = mods;
|
||||
return .{ .text = "", .composing = false };
|
||||
}
|
@ -9,7 +9,7 @@ const Key = @import("key.zig").Key;
|
||||
/// The full list of entries for the current platform.
|
||||
pub const entries: []const Entry = entries: {
|
||||
const native_idx = switch (builtin.os.tag) {
|
||||
.macos => 4, // mac
|
||||
.ios, .macos => 4, // mac
|
||||
.windows => 3, // win
|
||||
.linux => 2, // xkb
|
||||
else => @compileError("unsupported platform"),
|
||||
|
@ -1,3 +1,4 @@
|
||||
const std = @import("std");
|
||||
pub const cursor = @import("cursor.zig");
|
||||
pub const key = @import("key.zig");
|
||||
pub const termio = @import("termio.zig");
|
||||
|
@ -130,7 +130,7 @@ pub const std_options = struct {
|
||||
//
|
||||
// sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'
|
||||
//
|
||||
if (builtin.os.tag == .macos) {
|
||||
if (builtin.target.isDarwin()) {
|
||||
// Convert our levels to Mac levels
|
||||
const mac_level: macos.os.LogType = switch (level) {
|
||||
.debug => .debug,
|
||||
|
@ -52,6 +52,9 @@ pub fn launchedFromDesktop() bool {
|
||||
// TODO: This should have some logic to detect this. Perhaps std.builtin.subsystem
|
||||
.windows => false,
|
||||
|
||||
// iPhone/iPad is always launched from the "desktop"
|
||||
.ios => true,
|
||||
|
||||
else => @compileError("unsupported platform"),
|
||||
};
|
||||
}
|
||||
|
@ -14,6 +14,10 @@ pub inline fn home(buf: []u8) !?[]u8 {
|
||||
return switch (builtin.os.tag) {
|
||||
inline .linux, .macos => try homeUnix(buf),
|
||||
.windows => try homeWindows(buf),
|
||||
|
||||
// iOS doesn't have a user-writable home directory
|
||||
.ios => null,
|
||||
|
||||
else => @compileError("unimplemented"),
|
||||
};
|
||||
}
|
||||
|
@ -7,18 +7,20 @@ const log = std.log.scoped(.os);
|
||||
|
||||
/// The system-configured double-click interval if its available.
|
||||
pub fn clickInterval() ?u32 {
|
||||
// On macOS, we can ask the system.
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
const NSEvent = objc.getClass("NSEvent") orelse {
|
||||
log.err("NSEvent class not found. Can't get click interval.", .{});
|
||||
return null;
|
||||
};
|
||||
return switch (builtin.os.tag) {
|
||||
// On macOS, we can ask the system.
|
||||
.macos => macos: {
|
||||
const NSEvent = objc.getClass("NSEvent") orelse {
|
||||
log.err("NSEvent class not found. Can't get click interval.", .{});
|
||||
return null;
|
||||
};
|
||||
|
||||
// Get the interval and convert to ms
|
||||
const interval = NSEvent.msgSend(f64, objc.sel("doubleClickInterval"), .{});
|
||||
const ms = @as(u32, @intFromFloat(@ceil(interval * 1000)));
|
||||
return ms;
|
||||
}
|
||||
// Get the interval and convert to ms
|
||||
const interval = NSEvent.msgSend(f64, objc.sel("doubleClickInterval"), .{});
|
||||
const ms = @as(u32, @intFromFloat(@ceil(interval * 1000)));
|
||||
break :macos ms;
|
||||
},
|
||||
|
||||
return null;
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ pub fn open(alloc: Allocator, url: []const u8) !void {
|
||||
.linux => &.{ "xdg-open", url },
|
||||
.macos => &.{ "open", url },
|
||||
.windows => &.{ "rundll32", "url.dll,FileProtocolHandler", url },
|
||||
.ios => return error.Unimplemented,
|
||||
else => @compileError("unsupported OS"),
|
||||
};
|
||||
|
||||
|
39
src/pty.zig
39
src/pty.zig
@ -14,10 +14,41 @@ pub const winsize = extern struct {
|
||||
ws_ypixel: u16 = 600,
|
||||
};
|
||||
|
||||
pub const Pty = if (builtin.os.tag == .windows)
|
||||
WindowsPty
|
||||
else
|
||||
PosixPty;
|
||||
pub const Pty = switch (builtin.os.tag) {
|
||||
.windows => WindowsPty,
|
||||
.ios => NullPty,
|
||||
else => PosixPty,
|
||||
};
|
||||
|
||||
// A pty implementation that does nothing.
|
||||
//
|
||||
// TODO: This should be removed. This is only temporary until we have
|
||||
// a termio that doesn't use a pty. This isn't used in any user-facing
|
||||
// artifacts, this is just a stopgap to get compilation to work on iOS.
|
||||
const NullPty = struct {
|
||||
pub const Fd = std.os.fd_t;
|
||||
|
||||
master: Fd,
|
||||
slave: Fd,
|
||||
|
||||
pub fn open(size: winsize) !Pty {
|
||||
_ = size;
|
||||
return .{ .master = 0, .slave = 0 };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Pty) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn setSize(self: *Pty, size: winsize) !void {
|
||||
_ = self;
|
||||
_ = size;
|
||||
}
|
||||
|
||||
pub fn childPreExec(self: Pty) !void {
|
||||
_ = self;
|
||||
}
|
||||
};
|
||||
|
||||
/// Linux PTY creation and management. This is just a thin layer on top
|
||||
/// of Linux syscalls. The caller is responsible for detail-oriented handling
|
||||
|
Reference in New Issue
Block a user