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  }