123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156typet={fd:Unix.file_descr;mutablepid:intoption}typewinsize={rows:int;cols:int;xpixel:int;ypixel:int}(* External C functions *)externalopen_pty_raw:unit->Unix.file_descr*Unix.file_descr="ocaml_pty_open"externalget_winsize_raw:Unix.file_descr->winsize="ocaml_pty_get_winsize"externalset_winsize_raw:Unix.file_descr->winsize->unit="ocaml_pty_set_winsize_byte""ocaml_pty_set_winsize"externalsetsid_and_setctty:Unix.file_descr->unit="ocaml_pty_setsid_and_setctty"externalraise_fork_error:unit->'a="ocaml_raise_fork_error"letfile_descrt=t.fdletin_fdt=t.fdletout_fdt=t.fdletpidt=t.pidletterminatet=matcht.pidwith|Somepid->(tryUnix.killpidSys.sigtermwithUnix.Unix_error_->())|None->invalid_arg"Pty.terminate: no child process"letkillt=matcht.pidwith|Somepid->(tryUnix.killpidSys.sigkillwithUnix.Unix_error_->())|None->invalid_arg"Pty.kill: no child process"letclose?(wait=true)t=(* Terminate and reap child process if spawned *)(matcht.pidwith|Somepid->(* Try SIGTERM first for graceful shutdown *)(tryUnix.killpidSys.sigtermwithUnix.Unix_error_->());ifwaitthen((* Give process time to exit cleanly *)Unix.sleepf0.1;(* Try to reap - if still running, force kill *)matchUnix.waitpid[WNOHANG]pidwith|0,_->((* Still running - send SIGKILL and wait *)(tryUnix.killpidSys.sigkillwithUnix.Unix_error_->());tryignore(Unix.waitpid[]pid)withUnix.Unix_error_->())|_,_->(* Already exited *)())|None->());(* Mark as closed to prevent duplicate cleanup *)t.pid<-None;(* Close file descriptor *)tryUnix.closet.fdwithUnix.Unix_error_->()letget_winsizet=get_winsize_rawt.fdletset_winsizetws=set_winsize_rawt.fdwsletresizet~rows~cols=letws={rows;cols;xpixel=0;ypixel=0}inset_winsizetwsletinherit_size~src~dst=letws=get_winsizesrcinset_winsizedstwsletopen_pty?winsize()=letmaster_fd,slave_fd=open_pty_raw()inletmaster={fd=master_fd;pid=None}inletslave={fd=slave_fd;pid=None}in(* Set initial window size if provided *)(matchwinsizewith|Somews->(tryset_winsizeslavewswithe->closemaster;closeslave;raisee)|None->());(master,slave)letspawn?env?cwd?winsize~prog~args()=letpty_master,pty_slave=open_pty?winsize()inletargv=Array.of_list(prog::args)inmatchUnix.fork()with|-1->(* Fork failed *)closepty_master;closepty_slave;raise_fork_error()|0->((* Child process *)Unix.closepty_master.fd;(* Create new session and set controlling TTY *)(trysetsid_and_setcttypty_slave.fdwithUnix.Unix_error_->Unix.closepty_slave.fd;exit127);(* Redirect stdin, stdout, stderr to the slave pty *)Unix.dup2pty_slave.fdUnix.stdin;Unix.dup2pty_slave.fdUnix.stdout;Unix.dup2pty_slave.fdUnix.stderr;(* Close original slave fd if not stdin/stdout/stderr *)ifpty_slave.fd<>Unix.stdin&&pty_slave.fd<>Unix.stdout&&pty_slave.fd<>Unix.stderrthenUnix.closepty_slave.fd;(* Change directory if requested *)(matchcwdwith|Somedir->(tryUnix.chdirdirwithUnix.Unix_error(e,_,_)->exit(Obj.magice:int))|None->());(* Execute the program *)trymatchenvwith|None->Unix.execvpprogargv|Someenv_array->Unix.execvpeprogargvenv_arraywithUnix.Unix_error(e,_,_)->exit(Obj.magice:int))|pid->(* Parent process *)closepty_slave;(* Store PID for cleanup on close *)pty_master.pid<-Somepid;pty_masterletwith_pty?winsizef=letmaster,slave=open_pty?winsize()inFun.protect~finally:(fun()->(tryclosemasterwithUnix.Unix_error_->());trycloseslavewithUnix.Unix_error_->())(fun()->fmasterslave)letwith_spawn?env?cwd?winsize~prog~argsf=letpty=spawn?env?cwd?winsize~prog~args()inFun.protect~finally:(fun()->trycloseptywithUnix.Unix_error_->())(fun()->fpty)(* I/O operations *)letreadtbufofslen=Unix.readt.fdbufofslenletwritetbufofslen=Unix.writet.fdbufofslenletwrite_stringtstrofslen=Unix.write_substringt.fdstrofslen(* Non-blocking mode *)letset_nonblockt=Unix.set_nonblockt.fdletclear_nonblockt=Unix.clear_nonblockt.fd