//go:build windows package winkeyer // linectl_windows.go — DTR/RTS line control via EscapeCommFunction, the direct // Win32 method N1MM/WSJT/fldigi use. It sets ONE line at a time without a // GetCommState/SetCommState round-trip, so a held RTS (PTT) is NOT disturbed when // DTR (CW) is toggled. go.bug.st/serial drives the lines via SetCommState, which // on USB-serial adapters (the SCU-17's CP210x, FTDI) drops the held RTS the moment // DTR changes — the "rig drops to RX between words" bug this fixes. import ( "fmt" "strings" "golang.org/x/sys/windows" ) type winLineCtl struct { h windows.Handle } // openLineCtl opens a COM port for line control only (no data I/O). func openLineCtl(port string) (lineCtl, error) { name := strings.TrimSpace(port) if name == "" { return nil, fmt.Errorf("no serial port") } // The \\.\ prefix is required for COM10+ and harmless for COM1-9. if !strings.HasPrefix(name, `\\.\`) { name = `\\.\` + name } p, err := windows.UTF16PtrFromString(name) if err != nil { return nil, err } h, err := windows.CreateFile(p, windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, 0, 0) if err != nil { return nil, fmt.Errorf("open %s: %w", port, err) } // A minimal DCB so the driver is in a sane state. Baud is irrelevant for pure // line control, but leaving fDtr/fRtsControl at DISABLE means only our explicit // EscapeCommFunction calls drive the lines. var dcb windows.DCB if windows.GetCommState(h, &dcb) == nil { dcb.BaudRate = 9600 dcb.ByteSize = 8 dcb.Parity = 0 // NOPARITY dcb.StopBits = 0 // ONESTOPBIT // Clear DTR (bits 4-5) and RTS (bits 12-13) control fields → DISABLE, so the // lines idle low until we assert them, and no hardware flow control fights us. dcb.Flags &^= 0x00000030 dcb.Flags &^= 0x00003000 _ = windows.SetCommState(h, &dcb) } c := &winLineCtl{h: h} // Start both lines low (inactive) — the keyer's idle() will do this too. _ = c.setDTR(false) _ = c.setRTS(false) return c, nil } func (c *winLineCtl) setDTR(on bool) error { f := uint32(windows.CLRDTR) if on { f = windows.SETDTR } return windows.EscapeCommFunction(c.h, f) } func (c *winLineCtl) setRTS(on bool) error { f := uint32(windows.CLRRTS) if on { f = windows.SETRTS } return windows.EscapeCommFunction(c.h, f) } func (c *winLineCtl) close() error { return windows.CloseHandle(c.h) }