github.com/dolfly/pty@v1.2.1/pty_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 package pty 5 6 import ( 7 "os" 8 "syscall" 9 "unsafe" 10 ) 11 12 var ( 13 // NOTE(security): as noted by the comment of syscall.NewLazyDLL and syscall.LoadDLL 14 // user need to call internal/syscall/windows/sysdll.Add("kernel32.dll") to make sure 15 // the kernel32.dll is loaded from windows system path 16 // 17 // ref: https://pkg.go.dev/syscall@go1.13?GOOS=windows#LoadDLL 18 kernel32DLL = syscall.NewLazyDLL("kernel32.dll") 19 20 // https://docs.microsoft.com/en-us/windows/console/createpseudoconsole 21 createPseudoConsole = kernel32DLL.NewProc("CreatePseudoConsole") 22 closePseudoConsole = kernel32DLL.NewProc("ClosePseudoConsole") 23 24 deleteProcThreadAttributeList = kernel32DLL.NewProc("DeleteProcThreadAttributeList") 25 initializeProcThreadAttributeList = kernel32DLL.NewProc("InitializeProcThreadAttributeList") 26 27 // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute 28 updateProcThreadAttribute = kernel32DLL.NewProc("UpdateProcThreadAttribute") 29 30 resizePseudoConsole = kernel32DLL.NewProc("ResizePseudoConsole") 31 getConsoleScreenBufferInfo = kernel32DLL.NewProc("GetConsoleScreenBufferInfo") 32 ) 33 34 func open() (_ Pty, _ Tty, err error) { 35 pr, consoleW, err := os.Pipe() 36 if err != nil { 37 return nil, nil, err 38 } 39 40 consoleR, pw, err := os.Pipe() 41 if err != nil { 42 _ = consoleW.Close() 43 _ = pr.Close() 44 return nil, nil, err 45 } 46 47 defer func() { 48 if err != nil { 49 _ = consoleW.Close() 50 _ = pr.Close() 51 52 _ = pw.Close() 53 _ = consoleR.Close() 54 } 55 }() 56 57 err = createPseudoConsole.Find() 58 if err != nil { 59 return nil, nil, err 60 } 61 62 var consoleHandle syscall.Handle 63 r1, _, err := createPseudoConsole.Call( 64 (windowsCoord{X: 80, Y: 30}).Pack(), // size: default 80x30 window 65 consoleR.Fd(), // console input 66 consoleW.Fd(), // console output 67 0, // console flags, currently only PSEUDOCONSOLE_INHERIT_CURSOR supported 68 uintptr(unsafe.Pointer(&consoleHandle)), // console handler value return 69 ) 70 if r1 != 0 { 71 // S_OK: 0 72 return nil, nil, os.NewSyscallError("CreatePseudoConsole", err) 73 } 74 75 return &WindowsPty{ 76 handle: uintptr(consoleHandle), 77 r: pr, 78 w: pw, 79 consoleR: consoleR, 80 consoleW: consoleW, 81 }, &WindowsTty{ 82 handle: uintptr(consoleHandle), 83 r: consoleR, 84 w: consoleW, 85 }, nil 86 } 87 88 var _ Pty = (*WindowsPty)(nil) 89 90 type WindowsPty struct { 91 handle uintptr 92 r, w *os.File 93 94 consoleR, consoleW *os.File 95 } 96 97 func (p *WindowsPty) Fd() uintptr { 98 return p.handle 99 } 100 101 func (p *WindowsPty) Read(data []byte) (int, error) { 102 return p.r.Read(data) 103 } 104 105 func (p *WindowsPty) Write(data []byte) (int, error) { 106 return p.w.Write(data) 107 } 108 109 func (p *WindowsPty) WriteString(s string) (int, error) { 110 return p.w.WriteString(s) 111 } 112 113 func (p *WindowsPty) InputPipe() *os.File { 114 return p.w 115 } 116 117 func (p *WindowsPty) OutputPipe() *os.File { 118 return p.r 119 } 120 121 func (p *WindowsPty) Close() error { 122 _ = p.r.Close() 123 _ = p.w.Close() 124 125 _ = p.consoleR.Close() 126 _ = p.consoleW.Close() 127 128 err := closePseudoConsole.Find() 129 if err != nil { 130 return err 131 } 132 133 _, _, err = closePseudoConsole.Call(p.handle) 134 return err 135 } 136 137 var _ Tty = (*WindowsTty)(nil) 138 139 type WindowsTty struct { 140 handle uintptr 141 r, w *os.File 142 } 143 144 func (t *WindowsTty) Fd() uintptr { 145 return t.handle 146 } 147 148 func (t *WindowsTty) Read(p []byte) (int, error) { 149 return t.r.Read(p) 150 } 151 152 func (t *WindowsTty) Write(p []byte) (int, error) { 153 return t.w.Write(p) 154 } 155 156 func (t *WindowsTty) Close() error { 157 _ = t.r.Close() 158 return t.w.Close() 159 }