Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@ builds:
goos:
- linux
- darwin
- windows

goarch:
- amd64
- arm64

archives:
- id: default
# Windows users expect .zip; other platforms keep the default tarball.
format_overrides:
- goos: windows
formats: [zip]
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !windows

package sidecar

import (
Expand All @@ -11,6 +13,8 @@ import (

// watchWindowSize listens for SIGWINCH and updates the remote PTY size.
// It returns when done is closed.
//
// Windows has no SIGWINCH; see terminal_windows.go for the polling equivalent.
func watchWindowSize(fd int, sess *ssh.Session, done <-chan struct{}) {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
Expand Down
47 changes: 47 additions & 0 deletions internal/sidecar/terminal_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//go:build windows

package sidecar

import (
"time"

"golang.org/x/crypto/ssh"
"golang.org/x/term"
)

// windowSizePollInterval is how often the local terminal is measured on
// Windows. SIGWINCH does not exist there, so resize is detected by polling.
const windowSizePollInterval = 250 * time.Millisecond

// watchWindowSize polls the local terminal size and updates the remote PTY
// when it changes. It returns when done is closed.
//
// This is the Windows counterpart to the SIGWINCH-driven implementation in
// terminal_unix.go.
func watchWindowSize(fd int, sess *ssh.Session, done <-chan struct{}) {
ticker := time.NewTicker(windowSizePollInterval)
defer ticker.Stop()

lastW, lastH, err := term.GetSize(fd)
if err != nil {
// Not a measurable terminal; nothing useful to report.
lastW, lastH = 0, 0
}

for {
select {
case <-done:
return
case <-ticker.C:
w, h, err := term.GetSize(fd)
if err != nil {
continue
}
if w == lastW && h == lastH {
continue
}
lastW, lastH = w, h
_ = sess.WindowChange(h, w)
}
}
}
3 changes: 1 addition & 2 deletions internal/telemetry/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"os/exec"
"path/filepath"
"sync"
"syscall"

"github.com/segmentio/analytics-go/v3"

Expand Down Expand Up @@ -63,7 +62,7 @@ func (d *delegateDestination) send(in io.Reader) error {
cmd := exec.Command(bin, "receive-telemetry")
cmd.Stdout = io.Discard
cmd.Stderr = io.Discard
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} // avoid signals sent to the parent (e.g. Ctrl-C) reaching this subprocess too
detachProcess(cmd) // avoid signals sent to the parent (e.g. Ctrl-C) reaching this subprocess too
cmd.Env = append(os.Environ(),
receiver.EnvWriteKey+"="+d.writeKey,
receiver.EnvTelemetryEndpoint+"="+d.endpoint,
Expand Down
7 changes: 7 additions & 0 deletions internal/telemetry/delegate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -37,7 +38,13 @@ func runTestMain(m *testing.M) int {
}
defer os.RemoveAll(dir)

// Windows will not execute a file without an executable extension, and
// `go build -o` does not add one when the output path is explicit. Without
// this the stub builds fine but never starts, and the tests see zero events.
receiverBinPath = filepath.Join(dir, "receiverbin")
if runtime.GOOS == "windows" {
receiverBinPath += ".exe"
}
build := exec.Command("go", "build", "-o", receiverBinPath, "./testdata/receiverbin")
build.Stderr = os.Stderr
if err := build.Run(); err != nil {
Expand Down
14 changes: 14 additions & 0 deletions internal/telemetry/detach_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !windows

package telemetry

import (
"os/exec"
"syscall"
)

// detachProcess puts the child in its own process group so that signals sent
// to the parent's group (e.g. Ctrl-C) do not reach it.
func detachProcess(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
15 changes: 15 additions & 0 deletions internal/telemetry/detach_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build windows

package telemetry

import (
"os/exec"
"syscall"
)

// detachProcess puts the child in a new process group so that console control
// events sent to the parent's group (e.g. Ctrl-C) do not reach it. This is the
// Windows analogue of setpgid.
func detachProcess(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP}
}