Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
785a21c
fix(libghostty): isolate git when compiling ghostty from source
jacobaraujo7 Jul 26, 2026
002bed4
fix(libghostty): isolate source builds from consumer git metadata
elias8 Jul 26, 2026
c42444a
docs: add contributing guide
elias8 Jul 26, 2026
85f814b
perf(flterm): add terminal workload benchmarks
elias8 Jul 21, 2026
7aed0f4
perf(flterm): add terminal benchmarks (#117)
elias8 Jul 27, 2026
3db7554
chore(libghostty): prepare v0.0.12 release
elias8 Jul 28, 2026
096a68c
chore(libghostty): prepare v0.0.12 release (#122)
elias8 Jul 28, 2026
de2e794
chore: update asset hashes for v0.0.12
elias8 Jul 28, 2026
492d380
chore(flterm): prepare v0.0.5 release
elias8 Jul 28, 2026
609ee31
chore(flterm): prepare v0.0.5 release (#123)
elias8 Jul 28, 2026
c99cfd2
fix(flterm): recover orphaned text input connection
elias8 Jul 29, 2026
5196982
fix(flterm): reconnect displaced text input clients (#126)
elias8 Jul 29, 2026
fac9eb6
chore(libghostty): sync ghostty source and bindings
elias8 Jul 31, 2026
1e58f4f
feat(libghostty): expose scrollback limits
elias8 Jul 31, 2026
be1570a
feat(libghostty): expose desktop notifications
elias8 Jul 31, 2026
221ea57
feat(libghostty): expose progress reports
elias8 Jul 31, 2026
20870c3
feat(libghostty): expose upstream terminal APIs (#129)
elias8 Jul 31, 2026
a43aeec
feat(flterm): configure scrollback limits
elias8 Jul 31, 2026
c1afc71
feat(flterm): forward desktop notifications
elias8 Jul 31, 2026
01d7f5e
feat(flterm): forward progress reports
elias8 Jul 31, 2026
27fc596
feat(flterm): expose terminal options and callbacks (#130)
elias8 Jul 31, 2026
7eede45
fix(libghostty): remove iOS alignment fixer
elias8 Jul 31, 2026
1b9fca3
fix(libghostty): remove obsolete iOS alignment patch (#131)
elias8 Jul 31, 2026
9092994
fix(libghostty): compile ghostty for the CPU baseline, not native
jacobaraujo7 Jul 31, 2026
6e215fb
fix(libghostty): target iOS deployment versions
elias8 Jul 31, 2026
4fb2890
fix(libghostty): set iOS deployment target to version 13 (#133)
elias8 Jul 31, 2026
4a5594a
build: upgrade ffigen to 21
elias8 Aug 1, 2026
4389059
refactor(libghostty): use generated FFI allocators
elias8 Aug 1, 2026
7d08663
chore(ffi): upgrade ffigen and regenerate bindings (#134)
elias8 Aug 1, 2026
e25ca7e
ci(deps): bump dart-lang/setup-dart/.github/workflows/publish.yml
dependabot[bot] Aug 4, 2026
05fcc1f
ci(deps): bump dart-lang/setup-dart/.github/workflows/publish.yml fro…
elias8 Aug 4, 2026
74256b8
fix(flterm): avoid selection notifications during build
elias8 Aug 5, 2026
e4c6571
fix(flterm): avoid selection notifications during build (#138)
elias8 Aug 5, 2026
69e12ba
ci(deps): bump dart-lang/setup-dart/.github/workflows/publish.yml
dependabot[bot] Aug 6, 2026
6d9866b
ci(deps): bump dart-lang/setup-dart/.github/workflows/publish.yml fro…
elias8 Aug 6, 2026
ed6a451
docs(libghostty): trim the -Dtarget comment per review
jacobaraujo7 Aug 6, 2026
30b0b79
fix(libghostty): compile ghostty for the CPU baseline, not native (#132)
elias8 Aug 7, 2026
04734fa
fix(flterm): forward wheel to app under mouse tracking (internal TUI …
jacobaraujo7 Jul 23, 2026
6e355e8
fix(flterm): forward macOS trackpad (pan/zoom) scroll to app too
jacobaraujo7 Jul 23, 2026
ee95dfc
docs(flterm): translate forwarded-scroll comments to English
pretodev Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/benchmark-flterm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: benchmark flterm

on:
push:
branches: [main]
paths:
- 'packages/flterm/**'
- '!packages/flterm/**/*.md'
- 'packages/libghostty/**'
- '!packages/libghostty/**/*.md'
- 'pubspec.yaml'
- 'pubspec.lock'
- '.github/workflows/benchmark-flterm.yml'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: benchmark-flterm-main
cancel-in-progress: false

env:
ZIG_VERSION: "0.16.0"

jobs:
benchmark:
name: benchmark-flterm-macos
runs-on: macos-15
timeout-minutes: 30
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
with:
channel: stable
cache: true
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1
with:
version: ${{ env.ZIG_VERSION }}
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.pub-cache
key: pub-benchmark-flterm-${{ hashFiles('**/pubspec.lock') }}
restore-keys: pub-benchmark-flterm-

- name: run benchmark
id: benchmark
run: |
flutter pub get
dart run packages/flterm/tool/benchmarks/run.dart \
--output "$RUNNER_TEMP/flterm-benchmark-results" \
--revision "$GITHUB_SHA"

- name: publish benchmark summary
if: steps.benchmark.outcome == 'success'
run: cat "$RUNNER_TEMP/flterm-benchmark-results/report.md" >> "$GITHUB_STEP_SUMMARY"

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ always() && steps.benchmark.outcome != 'skipped' }}
with:
name: flterm-benchmark-${{ github.sha }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/flterm-benchmark-results
retention-days: 90
if-no-files-found: warn
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ jobs:
runner: macos-15
ext: dylib
- target: aarch64-ios
build_target: aarch64-ios.13.0
runner: macos-15
ext: dylib
- target: aarch64-ios-simulator
build_target: aarch64-ios.13.0-simulator
runner: macos-15
ext: dylib
extra_args: -Dcpu=apple_a17
Expand Down Expand Up @@ -137,7 +139,7 @@ jobs:
rm ghostty.tar.gz
- name: compile
working-directory: ghostty-src
run: zig build -Demit-lib-vt=true --release=fast -Dtarget=${{ matrix.target }} ${{ matrix.extra_args }}
run: zig build -Demit-lib-vt=true --release=fast -Dtarget=${{ matrix.build_target || matrix.target }} ${{ matrix.extra_args }}
- name: prepare artifact
id: artifact
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-flterm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
needs: [github-release]
permissions:
id-token: write
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1.8.0
with:
environment: pub.dev
working-directory: packages/flterm
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ jobs:
if: startsWith(github.ref_name, 'libghostty-v')
permissions:
id-token: write
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1.8.0
with:
environment: pub.dev
working-directory: packages/libghostty
36 changes: 36 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Contributing to libghostty

Contributions are welcome, and thank you for taking an interest in libghostty.
I want the project to be easy to improve without making it harder to
understand or maintain.

If you find a problem but do not understand how to fix it safely, please open
a clear issue instead of a pull request. Describe the problem, expected
behavior, affected packages and platforms, and anything else needed to
understand it. A well-written issue is a valuable contribution on its own.

## Pull requests

- **Scope.** Group each pull request around a clear purpose. Refactoring,
formatting, and other supporting work can be included when they belong to
the same logical change. Separate unrelated work so the pull request remains
easy to understand and review. Keep package boundaries intact. `libghostty`
must remain framework-agnostic.

- **AI use.** While I am fine with the use of AI and coding agents, I will
not accept output you have not carefully reviewed, understood, and
verified. You must be able to explain the resulting code, the decisions
behind it, and its effect on supported platforms. Pull requests that do
not meet this bar will be closed.

- **Testing.** Every behavior change must have appropriate test coverage.
Fixes should include a test that would fail without the fix. New behavior
should cover its expected use, important edge cases, and failures. Run
formatting, analysis, and the affected package tests before submitting.

- **Description.** Keep the pull request description concise and specific.
Clearly describe the problem, the solution, and how it was verified. Do
not submit an unfiltered summary generated by AI or include detail that
does not help reviewers understand the change.

Keep the commit history logical and free of temporary or cleanup commits.
2 changes: 2 additions & 0 deletions packages/flterm/.pubignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
/dart_test.yaml
/test/
/tool/
/example/integration_test/flterm_benchmark.dart
/example/test_driver/flterm_benchmark_driver.dart
42 changes: 42 additions & 0 deletions packages/flterm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
# Changelog

## Unreleased

### Fixed

- **Text input recovery**: terminal clients reconnect when another input client
takes the platform text input connection, including while composition is
active.

## 0.0.5

### Added

- **Clipboard writes**: `TerminalController.onClipboardWrite` forwards atomic,
binary-safe OSC 52 and iTerm2 clipboard requests so applications can apply
their own platform and security policies. If the callback throws, the
initiating write finishes terminal processing before rethrowing the error.
- **Idle scrollback compression**: attached terminal views schedule bounded
compression after terminal and viewport activity settles, reducing retained
scrollback memory without competing with rendering.

### Changed

- **Kitty graphics caching**: unchanged placement snapshots are reused across
frames, while image generations invalidate cached images and stale pending
decodes. This avoids repeated traversal, sorting, and eviction work without
displaying stale image data.
- **Color behavior**: `ColorPalette.generated()` uses libghostty palette
generation, and terminal color-scheme reports use perceived background
luminance.
- **Published package contents**: tests, benchmark tooling, generated API
documentation, build outputs, and coverage data are excluded.

### Fixed

- **IME preedit layout**: ZWJ emoji, skin-tone emoji sequences, combining
marks, and variation selectors are measured as grapheme clusters using
libghostty's terminal width rules.
- **Viewport scrolling**: scroll-controller pixel offsets map to absolute
terminal rows regardless of the current viewport position.
- **Windows text input**: platform text input binds to the `TerminalView`'s
owning Flutter view and reconnects when that view changes.

## 0.0.4

### Breaking
Expand Down
2 changes: 1 addition & 1 deletion packages/flterm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ libghostty-vt engine.

```yaml
dependencies:
flterm: ^0.0.4
flterm: ^0.0.5
```

On web, initialize the wasm module once before mounting any terminal:
Expand Down
10 changes: 10 additions & 0 deletions packages/flterm/example/integration_test/flterm_benchmark.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

import '../../tool/benchmarks/frame/suite.dart' as benchmark;

void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
benchmark.FltermBenchmarkSuite(binding).register();
}
2 changes: 2 additions & 0 deletions packages/flterm/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter

flutter:
uses-material-design: true
Expand Down
12 changes: 12 additions & 0 deletions packages/flterm/example/test_driver/flterm_benchmark_driver.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:integration_test/integration_test_driver.dart';

Future<void> main() {
return integrationDriver(
responseDataCallback: (data) {
if (data == null) {
throw const FormatException('Benchmark returned no report data.');
}
return writeResponseData(data, testOutputFilename: 'flterm_benchmarks');
},
);
}
3 changes: 3 additions & 0 deletions packages/flterm/lib/flterm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export 'package:libghostty/libghostty.dart'
ClipboardWriteCallback,
ClipboardWriteResult,
CursorShape,
DesktopNotification,
DeviceAttributesResponse,
Formatter,
FormatterExtra,
Expand All @@ -26,6 +27,8 @@ export 'package:libghostty/libghostty.dart'
SelectionGestureBehavior,
SelectionGestureBehaviors,
TerminalMode,
TerminalProgress,
TerminalProgressState,
TerminalScreen,
UnderlineStyle,
initializeForWeb;
Expand Down
43 changes: 32 additions & 11 deletions packages/flterm/lib/src/foundation/terminal_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ enum ScrollToBottom {
/// ```dart
/// final controller = TerminalController(
/// config: TerminalConfig(
/// scrollbackLimit: 5_000_000,
/// scrollbackMaxBytes: 5_000_000,
/// cursorBlink: true,
/// modes: {
/// ...TerminalConfig.defaultModes,
Expand Down Expand Up @@ -81,9 +81,17 @@ class TerminalConfig {

/// Maximum scrollback buffer size in bytes.
///
/// Defaults to 10,000,000 bytes. Set to 0 to disable scrollback entirely.
/// The terminal discards the oldest lines when this limit is reached.
final int scrollbackLimit;
/// Defaults to 10,000 bytes. Set to null for no limit, or 0 to disable
/// scrollback. When this and [scrollbackMaxLines] are set, the oldest
/// complete pages are discarded when either limit is reached.
final int? scrollbackMaxBytes;

/// Maximum number of physical scrollback rows.
///
/// Defaults to no limit. At least one normal page is retained. When this
/// and [scrollbackMaxBytes] are set, the oldest complete pages are
/// discarded when either limit is reached.
final int? scrollbackMaxLines;

/// Maximum bytes of Kitty graphics image storage.
///
Expand Down Expand Up @@ -152,14 +160,22 @@ class TerminalConfig {
this.enquiryResponse = '',
this.modes = defaultModes,
this.cursorStyle = .block,
this.scrollbackLimit = 10_000_000,
this.scrollbackMaxBytes = 10_000,
this.scrollbackMaxLines,
this.kittyImageStorageLimit = 64 * 1024 * 1024,
this.selectionClearOnTyping = true,
this.scrollToBottom = .onKeystroke,
this.deviceAttributes = const DeviceAttributesResponse(),
}) : assert(cols > 0, 'cols must be positive'),
assert(rows > 0, 'rows must be positive'),
assert(scrollbackLimit >= 0, 'scrollbackLimit must be non-negative'),
assert(
scrollbackMaxBytes == null || scrollbackMaxBytes >= 0,
'scrollbackMaxBytes must be non-negative',
),
assert(
scrollbackMaxLines == null || scrollbackMaxLines >= 0,
'scrollbackMaxLines must be non-negative',
),
assert(
kittyImageStorageLimit >= 0,
'kittyImageStorageLimit must be non-negative',
Expand All @@ -170,7 +186,8 @@ class TerminalConfig {
int get hashCode => Object.hash(
cols,
rows,
scrollbackLimit,
scrollbackMaxBytes,
scrollbackMaxLines,
kittyImageStorageLimit,
apcBufferLimit,
glyphProtocol,
Expand All @@ -189,7 +206,8 @@ class TerminalConfig {
other is TerminalConfig &&
cols == other.cols &&
rows == other.rows &&
scrollbackLimit == other.scrollbackLimit &&
scrollbackMaxBytes == other.scrollbackMaxBytes &&
scrollbackMaxLines == other.scrollbackMaxLines &&
kittyImageStorageLimit == other.kittyImageStorageLimit &&
apcBufferLimit == other.apcBufferLimit &&
glyphProtocol == other.glyphProtocol &&
Expand All @@ -205,7 +223,8 @@ class TerminalConfig {
TerminalConfig copyWith({
int? cols,
int? rows,
int? scrollbackLimit,
int? scrollbackMaxBytes,
int? scrollbackMaxLines,
int? kittyImageStorageLimit,
int? apcBufferLimit,
bool? glyphProtocol,
Expand All @@ -220,7 +239,8 @@ class TerminalConfig {
return TerminalConfig(
cols: cols ?? this.cols,
rows: rows ?? this.rows,
scrollbackLimit: scrollbackLimit ?? this.scrollbackLimit,
scrollbackMaxBytes: scrollbackMaxBytes ?? this.scrollbackMaxBytes,
scrollbackMaxLines: scrollbackMaxLines ?? this.scrollbackMaxLines,
kittyImageStorageLimit:
kittyImageStorageLimit ?? this.kittyImageStorageLimit,
apcBufferLimit: apcBufferLimit ?? this.apcBufferLimit,
Expand All @@ -240,7 +260,8 @@ class TerminalConfig {
String toString() =>
'TerminalConfig('
'cols: $cols, rows: $rows, '
'scrollbackLimit: $scrollbackLimit, '
'scrollbackMaxBytes: $scrollbackMaxBytes, '
'scrollbackMaxLines: $scrollbackMaxLines, '
'modes: ${modes.length} entries)';

static bool _modesEqual(
Expand Down
14 changes: 14 additions & 0 deletions packages/flterm/lib/src/widgets/terminal_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ abstract class TerminalController extends ChangeNotifier
/// Called when the working directory changes. Read [pwd] for the value.
VoidCallback? onPwdChanged;

/// Sets the callback for desktop notifications requested through OSC 9 or
/// OSC 777, or clears it if null.
///
/// Requests are untrusted. The application decides whether and how to
/// display them. Fires synchronously during [write].
set onDesktopNotification(ValueChanged<DesktopNotification>? callback);

/// Sets the callback for program progress reported through OSC 9;4, or
/// clears it if null.
///
/// The application decides how to present progress. Fires synchronously
/// during [write].
set onProgressReport(ValueChanged<TerminalProgress>? callback);

/// Called when the grid dimensions change. Forward to your backend.
OnResize? onResize;

Expand Down
Loading