mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-21 11:16:08 +03:00
Merge remote-tracking branch 'upstream/main' into titlebar-unzoom-button
This commit is contained in:
143
.github/workflows/release-pr.yml
vendored
143
.github/workflows/release-pr.yml
vendored
@ -149,3 +149,146 @@ jobs:
|
||||
r2-bucket: ghostty-pr
|
||||
source-dir: blob
|
||||
destination-dir: ./
|
||||
|
||||
build-macos-debug:
|
||||
runs-on: namespace-profile-ghostty-macos
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v26
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v14
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.5.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
curl -L https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-for-Swift-Package-Manager.zip > sparkle.zip
|
||||
unzip sparkle.zip
|
||||
echo "$(pwd)/bin" >> $GITHUB_PATH
|
||||
|
||||
# Load Build Number
|
||||
- name: Build Number
|
||||
run: |
|
||||
echo "GHOSTTY_BUILD=$(git rev-list --count head)" >> $GITHUB_ENV
|
||||
echo "GHOSTTY_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
# GhosttyKit is the framework that is built from Zig for our native
|
||||
# Mac app to access. Build this in release mode.
|
||||
- name: Build GhosttyKit
|
||||
run: nix develop -c zig build -Dstatic=true -Doptimize=Debug
|
||||
|
||||
# The native app is built with native XCode tooling. This also does
|
||||
# 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 -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.
|
||||
- name: Update Info.plist
|
||||
env:
|
||||
SPARKLE_KEY_PUB: ${{ secrets.PROD_MACOS_SPARKLE_KEY_PUB }}
|
||||
run: |
|
||||
# Version Info
|
||||
/usr/libexec/PlistBuddy -c "Set :GhosttyCommit $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $GHOSTTY_BUILD" "macos/build/Release/Ghostty.app/Contents/Info.plist"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
|
||||
|
||||
# Updater
|
||||
/usr/libexec/PlistBuddy -c "Set :SUPublicEDKey $SPARKLE_KEY_PUB" "macos/build/Release/Ghostty.app/Contents/Info.plist"
|
||||
|
||||
- name: Codesign app bundle
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
|
||||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
||||
run: |
|
||||
# Turn our base64-encoded certificate back to a regular .p12 file
|
||||
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
|
||||
|
||||
# We need to create a new keychain, otherwise using the certificate will prompt
|
||||
# with a UI dialog asking for the certificate password, which we can't
|
||||
# use in a headless CI environment
|
||||
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
|
||||
# Codesign Sparkle. Some notes here:
|
||||
# - The XPC services aren't used since we don't sandbox Ghostty,
|
||||
# but since they're part of the build, they still need to be
|
||||
# codesigned.
|
||||
# - The binaries in the "Versions" folders need to NOT be symlinks.
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc"
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate"
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app"
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework"
|
||||
|
||||
# Codesign the app bundle
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime --entitlements "macos/Ghostty.entitlements" macos/build/Release/Ghostty.app
|
||||
|
||||
- name: "Notarize app bundle"
|
||||
env:
|
||||
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
|
||||
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
|
||||
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
|
||||
run: |
|
||||
# Store the notarization credentials so that we can prevent a UI password dialog
|
||||
# from blocking the CI
|
||||
echo "Create keychain profile"
|
||||
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD"
|
||||
|
||||
# We can't notarize an app bundle directly, but we need to compress it as an archive.
|
||||
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
|
||||
# notarization service
|
||||
echo "Creating temp notarization archive"
|
||||
ditto -c -k --keepParent "macos/build/Release/Ghostty.app" "notarization.zip"
|
||||
|
||||
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
|
||||
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
|
||||
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
|
||||
# you're curious
|
||||
echo "Notarize app"
|
||||
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
|
||||
|
||||
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
|
||||
# validated by macOS even when an internet connection is not available.
|
||||
echo "Attach staple"
|
||||
xcrun stapler staple "macos/build/Release/Ghostty.app"
|
||||
|
||||
# Zip up the app
|
||||
- name: Zip App
|
||||
run: cd macos/build/Release && zip -9 -r --symlinks ../../../ghostty-macos-universal-debug.zip Ghostty.app
|
||||
|
||||
# Update Blob Storage
|
||||
- name: Prep R2 Storage
|
||||
run: |
|
||||
mkdir blob
|
||||
mkdir -p blob/${GHOSTTY_BUILD}
|
||||
cp ghostty-macos-universal-debug.zip blob/${GHOSTTY_BUILD}/ghostty-macos-universal-debug.zip
|
||||
- name: Upload to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_PR_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_PR_AWS_KEY }}
|
||||
r2-secret-access-key: ${{ secrets.CF_R2_PR_SECRET_KEY }}
|
||||
r2-bucket: ghostty-pr
|
||||
source-dir: blob
|
||||
destination-dir: ./
|
||||
|
19
.github/workflows/test.yml
vendored
19
.github/workflows/test.yml
vendored
@ -277,6 +277,25 @@ jobs:
|
||||
- name: Test Dynamic Build
|
||||
run: nix develop -c zig build -Dstatic=false
|
||||
|
||||
test-macos:
|
||||
runs-on: namespace-profile-ghostty-macos
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v26
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v14
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: test
|
||||
run: nix develop -c zig build test
|
||||
|
||||
prettier:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
timeout-minutes: 60
|
||||
|
19
build.zig
19
build.zig
@ -1055,11 +1055,14 @@ fn addDeps(
|
||||
"fontconfig",
|
||||
fontconfig_dep.module("fontconfig"),
|
||||
);
|
||||
if (config.font_backend.hasHarfbuzz()) step.root_module.addImport(
|
||||
"harfbuzz",
|
||||
harfbuzz_dep.module("harfbuzz"),
|
||||
);
|
||||
step.root_module.addImport("oniguruma", oniguruma_dep.module("oniguruma"));
|
||||
step.root_module.addImport("freetype", freetype_dep.module("freetype"));
|
||||
step.root_module.addImport("glslang", glslang_dep.module("glslang"));
|
||||
step.root_module.addImport("spirv_cross", spirv_cross_dep.module("spirv_cross"));
|
||||
step.root_module.addImport("harfbuzz", harfbuzz_dep.module("harfbuzz"));
|
||||
step.root_module.addImport("xev", libxev_dep.module("xev"));
|
||||
step.root_module.addImport("opengl", opengl_dep.module("opengl"));
|
||||
step.root_module.addImport("pixman", pixman_dep.module("pixman"));
|
||||
@ -1110,7 +1113,6 @@ fn addDeps(
|
||||
step.addIncludePath(freetype_dep.path(""));
|
||||
step.linkSystemLibrary2("bzip2", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("freetype2", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("harfbuzz", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("libpng", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("oniguruma", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("pixman-1", dynamic_link_opts);
|
||||
@ -1119,6 +1121,9 @@ fn addDeps(
|
||||
if (config.font_backend.hasFontconfig()) {
|
||||
step.linkSystemLibrary2("fontconfig", dynamic_link_opts);
|
||||
}
|
||||
if (config.font_backend.hasHarfbuzz()) {
|
||||
step.linkSystemLibrary2("harfbuzz", dynamic_link_opts);
|
||||
}
|
||||
}
|
||||
|
||||
// Other dependencies, we may dynamically link
|
||||
@ -1136,14 +1141,16 @@ fn addDeps(
|
||||
step.linkLibrary(freetype_dep.artifact("freetype"));
|
||||
try static_libs.append(freetype_dep.artifact("freetype").getEmittedBin());
|
||||
|
||||
// Harfbuzz
|
||||
step.linkLibrary(harfbuzz_dep.artifact("harfbuzz"));
|
||||
try static_libs.append(harfbuzz_dep.artifact("harfbuzz").getEmittedBin());
|
||||
|
||||
// Pixman
|
||||
step.linkLibrary(pixman_dep.artifact("pixman"));
|
||||
try static_libs.append(pixman_dep.artifact("pixman").getEmittedBin());
|
||||
|
||||
// Harfbuzz
|
||||
if (config.font_backend.hasHarfbuzz()) {
|
||||
step.linkLibrary(harfbuzz_dep.artifact("harfbuzz"));
|
||||
try static_libs.append(harfbuzz_dep.artifact("harfbuzz").getEmittedBin());
|
||||
}
|
||||
|
||||
// Only Linux gets fontconfig
|
||||
if (config.font_backend.hasFontconfig()) {
|
||||
// Fontconfig
|
||||
|
36
flake.lock
generated
36
flake.lock
generated
@ -52,11 +52,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -88,11 +88,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694102001,
|
||||
"narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=",
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -104,13 +104,13 @@
|
||||
"langref": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"narHash": "sha256-mYdDCBdNEIeMbavdhSo8qXqW+3fqPC8BAich7W3umrI=",
|
||||
"narHash": "sha256-94broSBethRhPJr0G9no4TPyB8ee6BQ/hHK1QnLPln0=",
|
||||
"type": "file",
|
||||
"url": "https://raw.githubusercontent.com/ziglang/zig/63bd2bff12992aef0ce23ae4b344e9cb5d65f05d/doc/langref.html.in"
|
||||
"url": "https://raw.githubusercontent.com/ziglang/zig/54bbc73f8502fe073d385361ddb34a43d12eec39/doc/langref.html.in"
|
||||
},
|
||||
"original": {
|
||||
"type": "file",
|
||||
"url": "https://raw.githubusercontent.com/ziglang/zig/63bd2bff12992aef0ce23ae4b344e9cb5d65f05d/doc/langref.html.in"
|
||||
"url": "https://raw.githubusercontent.com/ziglang/zig/54bbc73f8502fe073d385361ddb34a43d12eec39/doc/langref.html.in"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
@ -147,11 +147,11 @@
|
||||
},
|
||||
"nixpkgs-zig-0-12": {
|
||||
"locked": {
|
||||
"lastModified": 1711789504,
|
||||
"narHash": "sha256-1XRwW0MD9LxtHMMlPmF3rDw/Zbv4jLnpGnJEtibO+MQ=",
|
||||
"lastModified": 1712247214,
|
||||
"narHash": "sha256-7PTw86NnE2nCQPf+PPI/kOKwmlbbTqUthYSz/nDnAoc=",
|
||||
"owner": "vancluever",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c9e24149cca8215b84fc3ce5bc2bdc1ca823a588",
|
||||
"rev": "6726262c930716f601345b2c9d0c42ba069991b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -217,11 +217,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701390337,
|
||||
"narHash": "sha256-C+Lyio+GPl3B2IAZ6Nk5hAAE2g6a8bO9vMACUfOLC/g=",
|
||||
"lastModified": 1711627798,
|
||||
"narHash": "sha256-4BUZmgUFrrD5dRZbOUYRRQEDwLX/r7/ErLi+vHfB/+8=",
|
||||
"owner": "mitchellh",
|
||||
"repo": "zig-overlay",
|
||||
"rev": "1815ef2d0451b1121a6a91051da84906fcb06f99",
|
||||
"rev": "b01e0b81d1fa489e54362ea0a74f182eaa9a35bb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -241,11 +241,11 @@
|
||||
"zig-overlay": "zig-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1703036566,
|
||||
"narHash": "sha256-GKw+ON8FcUVHxDzA35piev/W/YUvXv5aZ4hmIzNFWuc=",
|
||||
"lastModified": 1711925513,
|
||||
"narHash": "sha256-DFgsGlEGsxLgtRrh7J+v8x4w+/cJatTCkrZP3/0Gb/o=",
|
||||
"owner": "zigtools",
|
||||
"repo": "zls",
|
||||
"rev": "adaeabbe1ba888d74309d0a837d4abddc24cf638",
|
||||
"rev": "4e01c08f558ea07462aaa7b71d2a24f86f47a855",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const base = @import("base.zig");
|
||||
const c = @import("c.zig");
|
||||
const cftype = @import("type.zig");
|
||||
const ComparisonResult = base.ComparisonResult;
|
||||
const Range = base.Range;
|
||||
@ -42,6 +43,14 @@ pub const Array = opaque {
|
||||
};
|
||||
|
||||
pub const MutableArray = opaque {
|
||||
pub fn create() Allocator.Error!*MutableArray {
|
||||
return CFArrayCreateMutable(
|
||||
null,
|
||||
0,
|
||||
&c.kCFTypeArrayCallBacks,
|
||||
) orelse error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn createCopy(array: *Array) Allocator.Error!*MutableArray {
|
||||
return CFArrayCreateMutableCopy(
|
||||
null,
|
||||
@ -54,6 +63,18 @@ pub const MutableArray = opaque {
|
||||
cftype.CFRelease(self);
|
||||
}
|
||||
|
||||
pub fn appendValue(
|
||||
self: *MutableArray,
|
||||
comptime Elem: type,
|
||||
value: *const Elem,
|
||||
) void {
|
||||
CFArrayAppendValue(self, @constCast(@ptrCast(value)));
|
||||
}
|
||||
|
||||
pub fn removeValue(self: *MutableArray, idx: usize) void {
|
||||
CFArrayRemoveValueAtIndex(self, idx);
|
||||
}
|
||||
|
||||
pub fn sortValues(
|
||||
self: *MutableArray,
|
||||
comptime Elem: type,
|
||||
@ -73,12 +94,24 @@ pub const MutableArray = opaque {
|
||||
);
|
||||
}
|
||||
|
||||
extern "c" fn CFArrayCreateMutable(
|
||||
allocator: ?*anyopaque,
|
||||
capacity: usize,
|
||||
callbacks: ?*const anyopaque,
|
||||
) ?*MutableArray;
|
||||
extern "c" fn CFArrayCreateMutableCopy(
|
||||
allocator: ?*anyopaque,
|
||||
capacity: usize,
|
||||
array: *Array,
|
||||
) ?*MutableArray;
|
||||
|
||||
extern "c" fn CFArrayAppendValue(
|
||||
*MutableArray,
|
||||
*anyopaque,
|
||||
) void;
|
||||
extern "c" fn CFArrayRemoveValueAtIndex(
|
||||
*MutableArray,
|
||||
usize,
|
||||
) void;
|
||||
extern "c" fn CFArraySortValues(
|
||||
array: *MutableArray,
|
||||
range: Range,
|
||||
|
@ -6,8 +6,8 @@ const c = @import("c.zig");
|
||||
|
||||
pub const Dictionary = opaque {
|
||||
pub fn create(
|
||||
keys: ?[]?*const anyopaque,
|
||||
values: ?[]?*const anyopaque,
|
||||
keys: ?[]const ?*const anyopaque,
|
||||
values: ?[]const ?*const anyopaque,
|
||||
) Allocator.Error!*Dictionary {
|
||||
if (keys != null or values != null) {
|
||||
assert(keys != null);
|
||||
@ -17,8 +17,8 @@ pub const Dictionary = opaque {
|
||||
|
||||
return @as(?*Dictionary, @ptrFromInt(@intFromPtr(c.CFDictionaryCreate(
|
||||
null,
|
||||
@ptrCast(if (keys) |slice| slice.ptr else null),
|
||||
@ptrCast(if (values) |slice| slice.ptr else null),
|
||||
@constCast(@ptrCast(if (keys) |slice| slice.ptr else null)),
|
||||
@constCast(@ptrCast(if (values) |slice| slice.ptr else null)),
|
||||
@intCast(if (keys) |slice| slice.len else 0),
|
||||
&c.kCFTypeDictionaryKeyCallBacks,
|
||||
&c.kCFTypeDictionaryValueCallBacks,
|
||||
|
@ -2146,7 +2146,7 @@ pub fn mouseButtonCallback(
|
||||
const pos = try self.rt_surface.getCursorPos();
|
||||
const point = self.posToViewport(pos.x, pos.y);
|
||||
const screen = &self.renderer_state.terminal.screen;
|
||||
const p = screen.pages.pin(.{ .active = point }) orelse {
|
||||
const p = screen.pages.pin(.{ .viewport = point }) orelse {
|
||||
log.warn("failed to get pin for clicked point", .{});
|
||||
return;
|
||||
};
|
||||
|
@ -839,7 +839,9 @@ keybind: Keybinds = .{},
|
||||
///
|
||||
/// * `sudo` - Set sudo wrapper to preserve terminfo.
|
||||
///
|
||||
/// Example: `cursor`, `no-cursor`, `sudo`, `no-sudo`
|
||||
/// * `title` - Set the window title via shell integration.
|
||||
///
|
||||
/// Example: `cursor`, `no-cursor`, `sudo`, `no-sudo`, `title`, `no-title`
|
||||
@"shell-integration-features": ShellIntegrationFeatures = .{},
|
||||
|
||||
/// Sets the reporting format for OSC sequences that request color information.
|
||||
@ -3378,6 +3380,7 @@ pub const ShellIntegration = enum {
|
||||
pub const ShellIntegrationFeatures = packed struct {
|
||||
cursor: bool = true,
|
||||
sudo: bool = false,
|
||||
title: bool = true,
|
||||
};
|
||||
|
||||
/// OSC 4, 10, 11, and 12 default color reporting format.
|
||||
|
@ -13,8 +13,9 @@ pub const Face = struct {
|
||||
/// Our font face
|
||||
font: *macos.text.Font,
|
||||
|
||||
/// Harfbuzz font corresponding to this face.
|
||||
hb_font: harfbuzz.Font,
|
||||
/// Harfbuzz font corresponding to this face. We only use this
|
||||
/// if we're using Harfbuzz.
|
||||
hb_font: if (harfbuzz_shaper) harfbuzz.Font else void,
|
||||
|
||||
/// The presentation for this font.
|
||||
presentation: font.Presentation,
|
||||
@ -25,6 +26,10 @@ pub const Face = struct {
|
||||
/// Set quirks.disableDefaultFontFeatures
|
||||
quirks_disable_default_font_features: bool = false,
|
||||
|
||||
/// True if our build is using Harfbuzz. If we're not, we can avoid
|
||||
/// some Harfbuzz-specific code paths.
|
||||
const harfbuzz_shaper = font.options.backend.hasHarfbuzz();
|
||||
|
||||
/// The matrix applied to a regular font to auto-italicize it.
|
||||
pub const italic_skew = macos.graphics.AffineTransform{
|
||||
.a = 1,
|
||||
@ -75,10 +80,6 @@ pub const Face = struct {
|
||||
/// Initialize a face with a CTFont. This will take ownership over
|
||||
/// the CTFont. This does NOT copy or retain the CTFont.
|
||||
pub fn initFont(ct_font: *macos.text.Font, opts: font.face.Options) !Face {
|
||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||
errdefer hb_font.destroy();
|
||||
hb_font.setScale(opts.size.pixels(), opts.size.pixels());
|
||||
|
||||
const traits = ct_font.getSymbolicTraits();
|
||||
const metrics = metrics: {
|
||||
var metrics = try calcMetrics(ct_font);
|
||||
@ -86,6 +87,13 @@ pub const Face = struct {
|
||||
break :metrics metrics;
|
||||
};
|
||||
|
||||
var hb_font = if (comptime harfbuzz_shaper) font: {
|
||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||
hb_font.setScale(opts.size.pixels(), opts.size.pixels());
|
||||
break :font hb_font;
|
||||
} else {};
|
||||
errdefer if (comptime harfbuzz_shaper) hb_font.destroy();
|
||||
|
||||
var result: Face = .{
|
||||
.font = ct_font,
|
||||
.hb_font = hb_font,
|
||||
@ -144,7 +152,7 @@ pub const Face = struct {
|
||||
|
||||
pub fn deinit(self: *Face) void {
|
||||
self.font.release();
|
||||
self.hb_font.destroy();
|
||||
if (comptime harfbuzz_shaper) self.hb_font.destroy();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,19 @@ pub const Backend = enum {
|
||||
=> false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasHarfbuzz(self: Backend) bool {
|
||||
return switch (self) {
|
||||
.freetype,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
=> true,
|
||||
|
||||
.coretext,
|
||||
.web_canvas,
|
||||
=> false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// The styles that a family can take.
|
||||
|
BIN
src/font/res/JetBrainsMonoNerdFont-Regular.ttf
Normal file
BIN
src/font/res/JetBrainsMonoNerdFont-Regular.ttf
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
const builtin = @import("builtin");
|
||||
const options = @import("main.zig").options;
|
||||
const harfbuzz = @import("shaper/harfbuzz.zig");
|
||||
const coretext = @import("shaper/coretext.zig");
|
||||
pub const harfbuzz = @import("shaper/harfbuzz.zig");
|
||||
pub const coretext = @import("shaper/coretext.zig");
|
||||
pub const web_canvas = @import("shaper/web_canvas.zig");
|
||||
pub usingnamespace @import("shaper/run.zig");
|
||||
|
||||
@ -10,12 +10,12 @@ pub const Shaper = switch (options.backend) {
|
||||
.freetype,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
.coretext,
|
||||
=> harfbuzz.Shaper,
|
||||
|
||||
// Has missing features, can't be used yet. See the comments in
|
||||
// the coretext.zig file for more details.
|
||||
//.coretext => coretext.Shaper,
|
||||
// Note that coretext_freetype cannot use the coretext
|
||||
// shaper because the coretext shaper requests CoreText
|
||||
// font faces.
|
||||
.coretext => coretext.Shaper,
|
||||
|
||||
.web_canvas => web_canvas.Shaper,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,9 @@ pub const fontEmoji = @embedFile("res/NotoColorEmoji.ttf");
|
||||
pub const fontEmojiText = @embedFile("res/NotoEmoji-Regular.ttf");
|
||||
pub const fontVariable = @embedFile("res/Lilex-VF.ttf");
|
||||
|
||||
/// Font with nerd fonts embedded.
|
||||
pub const fontNerdFont = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf");
|
||||
|
||||
/// Cozette is a unique font because it embeds some emoji characters
|
||||
/// but has a text presentation.
|
||||
pub const fontCozette = @embedFile("res/CozetteVector.ttf");
|
||||
|
@ -254,10 +254,19 @@ fn legacy(
|
||||
self.ignore_keypad_with_numlock,
|
||||
self.modify_other_keys_state_2,
|
||||
)) |sequence| pc_style: {
|
||||
// If we're pressing enter and have UTF-8 text, we probably are
|
||||
// clearing a dead key state. This happens specifically on macOS.
|
||||
// If we're pressing enter or escape and have UTF-8 text, we probably
|
||||
// are clearing a dead key state. This happens specifically on macOS.
|
||||
// "Clearing" a dead key state may or may not commit the dead key
|
||||
// state; this differs by language:
|
||||
//
|
||||
// - Japanese clears and does not write the characters.
|
||||
// - Korean clears and writes the characters.
|
||||
//
|
||||
// We have a unit test for this.
|
||||
if (self.event.key == .enter and self.event.utf8.len > 0) {
|
||||
if ((self.event.key == .enter or
|
||||
self.event.key == .escape) and
|
||||
self.event.utf8.len > 0)
|
||||
{
|
||||
break :pc_style;
|
||||
}
|
||||
|
||||
@ -1649,6 +1658,20 @@ test "legacy: enter with utf8 (dead key state)" {
|
||||
try testing.expectEqualStrings("A", actual);
|
||||
}
|
||||
|
||||
test "legacy: esc with utf8 (dead key state)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var enc: KeyEncoder = .{
|
||||
.event = .{
|
||||
.key = .escape,
|
||||
.utf8 = "A",
|
||||
.unshifted_codepoint = 0x0D,
|
||||
},
|
||||
};
|
||||
|
||||
const actual = try enc.legacy(&buf);
|
||||
try testing.expectEqualStrings("A", actual);
|
||||
}
|
||||
|
||||
test "legacy: ctrl+shift+minus (underscore on US)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var enc: KeyEncoder = .{
|
||||
|
@ -256,7 +256,9 @@ pub const GlobalState = struct {
|
||||
std.log.info("ghostty build optimize={s}", .{build_config.mode_string});
|
||||
std.log.info("runtime={}", .{build_config.app_runtime});
|
||||
std.log.info("font_backend={}", .{build_config.font_backend});
|
||||
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
||||
if (comptime build_config.font_backend.hasHarfbuzz()) {
|
||||
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
||||
}
|
||||
if (comptime build_config.font_backend.hasFontconfig()) {
|
||||
std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
||||
}
|
||||
|
@ -70,9 +70,11 @@ function __ghostty_precmd() {
|
||||
}
|
||||
fi
|
||||
|
||||
# Command
|
||||
PS0=$PS0'$(__ghostty_get_current_command)'
|
||||
PS1=$PS1'\[\e]2;$PWD\a\]'
|
||||
if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_TITLE" != 1 ]]; then
|
||||
# Command
|
||||
PS0=$PS0'$(__ghostty_get_current_command)'
|
||||
PS1=$PS1'\[\e]2;$PWD\a\]'
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$_ghostty_executing" != "0"; then
|
||||
|
@ -194,11 +194,13 @@ _ghostty_deferred_init() {
|
||||
_ghostty_report_pwd"
|
||||
_ghostty_report_pwd
|
||||
|
||||
# Enable terminal title changes.
|
||||
functions[_ghostty_precmd]+="
|
||||
builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(%):-%(4~|…/%3~|%~)}\"\$'\\a'"
|
||||
functions[_ghostty_preexec]+="
|
||||
builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(V)1}\"\$'\\a'"
|
||||
if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_TITLE" != 1 ]]; then
|
||||
# Enable terminal title changes.
|
||||
functions[_ghostty_precmd]+="
|
||||
builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(%):-%(4~|…/%3~|%~)}\"\$'\\a'"
|
||||
functions[_ghostty_preexec]+="
|
||||
builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(V)1}\"\$'\\a'"
|
||||
fi
|
||||
|
||||
if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_CURSOR" != 1 ]]; then
|
||||
# Enable cursor shape changes depending on the current keymap.
|
||||
|
@ -1355,14 +1355,14 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) !
|
||||
defer if (mapbuilder) |*b| b.deinit();
|
||||
|
||||
const sel_ordered = opts.sel.ordered(self, .forward);
|
||||
const sel_start = start: {
|
||||
var start = sel_ordered.start();
|
||||
const sel_start: Pin = start: {
|
||||
var start: Pin = sel_ordered.start();
|
||||
const cell = start.rowAndCell().cell;
|
||||
if (cell.wide == .spacer_tail) start.x -= 1;
|
||||
break :start start;
|
||||
};
|
||||
const sel_end = end: {
|
||||
var end = sel_ordered.end();
|
||||
const sel_end: Pin = end: {
|
||||
var end: Pin = sel_ordered.end();
|
||||
const cell = end.rowAndCell().cell;
|
||||
switch (cell.wide) {
|
||||
.narrow, .wide => {},
|
||||
@ -1433,7 +1433,9 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) !
|
||||
}
|
||||
}
|
||||
|
||||
if (row_count < rows.len - 1 and
|
||||
const is_final_row = chunk.page == sel_end.page and y == sel_end.y;
|
||||
|
||||
if (!is_final_row and
|
||||
(!row.wrap or sel_ordered.rectangle))
|
||||
{
|
||||
try strbuilder.append('\n');
|
||||
@ -7073,6 +7075,38 @@ test "Screen: selectionString, rectangle, more complex w/breaks" {
|
||||
try testing.expectEqualStrings(expected, contents);
|
||||
}
|
||||
|
||||
test "Screen: selectionString multi-page" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, 1, 3, 2048);
|
||||
defer s.deinit();
|
||||
|
||||
const first_page_size = s.pages.pages.first.?.data.capacity.rows;
|
||||
|
||||
// Lazy way to seek to the first page boundary.
|
||||
for (0..first_page_size - 1) |_| {
|
||||
try s.testWriteString("\n");
|
||||
}
|
||||
|
||||
try s.testWriteString("y\ny\ny");
|
||||
|
||||
{
|
||||
const sel = Selection.init(
|
||||
s.pages.pin(.{ .active = .{ .x = 0, .y = 0 } }).?,
|
||||
s.pages.pin(.{ .active = .{ .x = 0, .y = 2 } }).?,
|
||||
false,
|
||||
);
|
||||
const contents = try s.selectionString(alloc, .{
|
||||
.sel = sel,
|
||||
.trim = false,
|
||||
});
|
||||
defer alloc.free(contents);
|
||||
const expected = "y\ny\ny";
|
||||
try testing.expectEqualStrings(expected, contents);
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: lineIterator" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
@ -50,6 +50,7 @@ pub fn setup(
|
||||
// Setup our feature env vars
|
||||
if (!features.cursor) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_CURSOR", "1");
|
||||
if (!features.sudo) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_SUDO", "1");
|
||||
if (!features.title) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_TITLE", "1");
|
||||
|
||||
return shell;
|
||||
}
|
||||
|
Reference in New Issue
Block a user