Skip to content

Fix auto-scroll and SIGINT/Ctrl+C on macOS#430

Open
botsdown wants to merge 3 commits intomigueldeicaza:mainfrom
botsdown:fix/macos-autoscroll-timer
Open

Fix auto-scroll and SIGINT/Ctrl+C on macOS#430
botsdown wants to merge 3 commits intomigueldeicaza:mainfrom
botsdown:fix/macos-autoscroll-timer

Conversation

@botsdown
Copy link

@botsdown botsdown commented Jan 9, 2026

This PR contains two fixes for macOS:

1. Fix auto-scroll timer for drag selection

When drag-selecting text past the terminal edges, the view should auto-scroll. This fix addresses multiple issues:

  1. Added timer management:

    • autoScrollTimer property to hold the timer reference
    • startAutoScrollTimer() to create timer with .common run loop mode
    • stopAutoScrollTimer() to invalidate and clean up
    • Timer start/stop calls in mouseDragged() and cleanup in mouseUp()
  2. Fixed scroll direction in scrollingTimerElapsed():

    • Was calling scrollUp() in both branches
    • Now correctly calls scrollDown() when autoScrollDelta > 0
  3. Added selection extension during scroll:

    • Calls selection.dragExtend() after scrolling to include newly visible rows
  4. Fixed scroll-down detection in mouseDragged():

    • calculateMouseHit() clamps row to valid range, so scroll-down condition was never triggered
    • Now uses raw pixel coordinates from mouse event to detect when mouse is outside view bounds

Uses .common run loop mode to ensure timer fires during mouse tracking.


2. Fix SIGINT/Ctrl+C by forcing forkpty on macOS

Problem

On modern macOS with swift-subprocess available, LocalProcess.startProcess() uses the startProcessWithSubprocess() path. This breaks Ctrl+C (SIGINT) because:

  1. The Subprocess path uses POSIX_SPAWN_SETSID which creates a new session without a controlling terminal
  2. Without a controlling terminal, the kernel's line discipline doesn't convert ETX (0x03) to SIGINT
  3. tcgetpgrp(childfd) returns -1 (ENOTTY) because there's no controlling terminal
  4. shellPid is never set in the Subprocess path (remains 0)

The result: pressing Ctrl+C shows ^C but doesn't interrupt the running process.

Solution

Force the forkpty path on macOS. The forkpty function internally calls login_tty() which:

  • Calls setsid() to create a new session
  • Calls ioctl(TIOCSCTTY) to set the PTY slave as the controlling terminal
  • Properly sets up stdin/stdout/stderr

This is a minimal change - just a #if os(macOS) conditional to prefer forkpty on macOS while preserving the Subprocess path for other platforms.

Testing

  1. Run a terminal with SwiftTerm on macOS
  2. Execute sleep 100
  3. Press Ctrl+C
  4. Before fix: ^C appears but sleep continues
  5. After fix: Process terminates immediately, echo $? shows 130 (128 + signal 2)

References

alowry-lokion and others added 2 commits January 9, 2026 17:09
When drag-selecting text past the terminal edges, the view should
auto-scroll. This fix addresses multiple issues:

1. Added timer management:
   - autoScrollTimer property to hold the timer reference
   - startAutoScrollTimer() to create timer with .common run loop mode
   - stopAutoScrollTimer() to invalidate and clean up
   - Timer start/stop calls in mouseDragged() and cleanup in mouseUp()

2. Fixed scroll direction in scrollingTimerElapsed():
   - Was calling scrollUp() in both branches
   - Now correctly calls scrollDown() when autoScrollDelta > 0

3. Added selection extension during scroll:
   - Calls selection.dragExtend() after scrolling to include newly
     visible rows

4. Fixed scroll-down detection in mouseDragged():
   - calculateMouseHit() clamps row to valid range, so scroll-down
     condition was never triggered
   - Now uses raw pixel coordinates from mouse event to detect when
     mouse is outside view bounds

Uses .common run loop mode to ensure timer fires during mouse tracking.
The Subprocess path uses POSIX_SPAWN_SETSID which creates a new session
without a controlling terminal. This breaks Ctrl+C (SIGINT) because:

1. The kernel's line discipline only generates SIGINT from ETX (^C) when
   the PTY slave is the controlling terminal
2. Without a controlling terminal, tcgetpgrp() returns -1 (ENOTTY)
3. shellPid is never set in the Subprocess path

The forkpty path works correctly because it calls login_tty() which:
- Calls setsid() to create a new session
- Calls ioctl(TIOCSCTTY) to set the PTY as controlling terminal
- Properly sets up stdin/stdout/stderr

This change forces the forkpty path on macOS while preserving the
Subprocess path for other platforms where it may be needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@botsdown botsdown changed the title Fix auto-scroll timer for drag selection on macOS Fix auto-scroll and SIGINT/Ctrl+C on macOS Jan 10, 2026
@migueldeicaza
Copy link
Owner

How does Control-C work, I am using it just fine, can you show me what seems to be the problem?

@migueldeicaza
Copy link
Owner

The auto-scroll seems sensible, I think I can add that.

@migueldeicaza
Copy link
Owner

Btw, I tried the reproduction steps you have, and for me, I get exactly that without your change:

image

@botsdown
Copy link
Author

botsdown commented Jan 10, 2026 via email

@botsdown
Copy link
Author

botsdown commented Jan 11, 2026 via email

@migueldeicaza
Copy link
Owner

I am on the aubprocess path

@botsdown
Copy link
Author

botsdown commented Jan 12, 2026 via email

@migueldeicaza
Copy link
Owner

I bet I know what it is.

Did you remove the Sandbox entitlement? I bet the sandbox is failing.

@botsdown
Copy link
Author

botsdown commented Jan 16, 2026 via email

@migueldeicaza
Copy link
Owner

Any updates?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants