# HG changeset patch # User Claus Gittinger # Date 1524834796 -7200 # Node ID 3e5713fe4685949c1d3c5cff37486bad5e9b58a8 # Parent 4d145bdadcf8187f1d46419beb19c846056cbbc0 #BUGFIX by cg class: TerminalSession added: #closeDownShell: removed: #killShell comment/format in: #closeDownShell #defineWindowSizeLines:columns: #startCommand:in:environment:setupTerminalWith:terminatedAction: changed: #outputFromAction:prompt:timeout:to: #readerProcessLoop #startReaderProcess #stopReaderProcess class: TerminalSession class added: #examples comment/format in: #documentation #initialize diff -r 4d145bdadcf8 -r 3e5713fe4685 TerminalSession.st --- a/TerminalSession.st Thu Apr 26 10:17:02 2018 +0200 +++ b/TerminalSession.st Fri Apr 27 15:13:16 2018 +0200 @@ -1,3 +1,5 @@ +"{ Encoding: utf8 }" + " COPYRIGHT (c) 2013 by eXept Software AG All Rights Reserved @@ -43,6 +45,8 @@ documentation " + This is basically a TerminalView without a view. + This keeps the state and API to interact with another program via a terminal session. Under Unix, a pseudo-tty connection is used; other operating systems might use other mechanisms. @@ -67,6 +71,37 @@ inStream - the controlled program's input (a pty-half) errStream - the controlled program's output (a pty-half) " +! + +examples +" + |session| + + session := TerminalSession new. + session startReaderProcess. + session + pluggableProcessInputAction:[:buffer :n | + Transcript show:(buffer copyTo:n). + Transcript endEntry. + ]. + session + startCommand:'ls -l' in:'~' + environment:nil + setupTerminalWith:[] + terminatedAction:[ Transcript showCR:'finished' ]. + + session + startCommand:'(cd ~/work/cg/schemeNew ; make)' in:'~' + environment:nil + setupTerminalWith:[] + terminatedAction:[ Transcript showCR:'finished' ]. + + session + startCommand:'(ls ~/work/cg/schemeNew)' in:'~' + environment:nil + setupTerminalWith:[] + terminatedAction:[ Transcript showCR:'finished' ]. +" ! ! !TerminalSession class methodsFor:'initialization'! @@ -77,6 +112,7 @@ " self initialize + Debug := true. " ! ! @@ -144,19 +180,32 @@ closeDownShell "shut down my shell process" - |pid| + self closeDownShell:1 +! + +closeDownShell:waitTimeInSeconds + "shut down my shell process" + + |pid waitTime| (pid := shellPid) notNil ifTrue:[ + Logger info:'killing shell pid=%1' with:shellPid. Debug ifTrue:[ - Transcript show:'killing shell pid='; showCR:pid. + Logger info:'killing shell pid=%1' with:pid. ]. - OperatingSystem isMSWINDOWSlike ifFalse:[ - OperatingSystem terminateProcessGroup:pid. - ]. + OperatingSystem terminateProcessGroup:pid. OperatingSystem terminateProcess:pid. - Delay waitForSeconds:0.2. + + waitTime := 0. + [shellPid notNil and:[waitTime < waitTimeInSeconds]] whileTrue:[ + Delay waitForSeconds:0.1. + waitTime := waitTime + 0.1 + ]. + Logger info:'shell pid after SIGTERM=%1' with:shellPid. + + "/ still not dead? shellPid notNil ifTrue:[ - "/ Delay waitForSeconds:1. + Logger info:'stil not dead after %1' with:waitTimeInSeconds. OperatingSystem isMSWINDOWSlike ifFalse:[ OperatingSystem killProcessGroup:pid. ]. @@ -256,29 +305,6 @@ ]. ! -killShell - "shut down my shell process and stop the background reader thread." - - |pid| - - (pid := shellPid) notNil ifTrue:[ - Debug ifTrue:[ - Transcript show:'killing shell pid='; showCR:pid. - ]. - OperatingSystem terminateProcessGroup:pid. - OperatingSystem terminateProcess:pid. - Delay waitForSeconds:1. - shellPid notNil ifTrue:[ - OperatingSystem isMSWINDOWSlike ifFalse:[ - OperatingSystem killProcessGroup:pid. - ]. - OperatingSystem killProcess:pid. - shellPid := nil. - ]. - OperatingSystem closePid:pid. - ]. -! - reinitialize shellPid := nil. inStream := outStream := errStream := nil. @@ -328,8 +354,7 @@ shellPid := Processor monitor:[ Debug ifTrue:[ - Transcript show:'exec:'; show:shell. - Transcript show:' args:'; showCR:args. + Logger info:'exec: "%1" args: "%2"' with:shell with:args. ]. OperatingSystem exec:shell @@ -343,10 +368,10 @@ ] action:[:status | Debug ifTrue:[ - Transcript show:'pid:'; showCR:status pid. - Transcript show:'status:'; showCR:status status. - Transcript show:'code:'; showCR:status code. - Transcript show:'core:'; showCR:status core. + Logger info:'pid: %1' with:status pid. + Logger info:'status %1:' with:status status. + Logger info:'code: %1' with:status code. + Logger info:'core %1:' with:status core. ]. status stillAlive ifFalse:[ exitStatus := status. @@ -356,6 +381,8 @@ ]. ]. + Logger info:'started shell pid: %1' with:shellPid. + "close the slave side of the pty/pipes (only used by the child)" pty notNil ifTrue:[ (pty at:2) close. @@ -437,10 +464,7 @@ ! defineWindowSizeLines:numberOfLines columns:numberOfColumns - | delta prevNumCols prevNumLines| - "/ any idea, how to do this under windows ? - OperatingSystem isUNIXlike ifTrue:[ "/ "/ tell the pty; @@ -450,14 +474,14 @@ and:[inStream isExternalStream and:[inStream isOpen]]) ifTrue:[ Debug ifTrue:[ - Transcript showCR:'TerminalSession [info]: changed len to ', numberOfLines printString. + Logger info:'TerminalSession [info]: changed len to %1' with:numberOfLines. ]. (OperatingSystem setWindowSizeOnFileDescriptor:inStream fileDescriptor width:numberOfColumns height:numberOfLines) ifFalse:[ Debug ifTrue:[ - Transcript showCR:'TerminalSession [warning]: cannot change windowSize'. + Logger info:'TerminalSession [warning]: cannot change windowSize'. ]. ]. @@ -534,9 +558,9 @@ (gotPrompt := (sema waitWithTimeout:seconds) notNil) ifFalse:[ newSize := collectedOutput size. self debuggingCodeFor:#cg is:[ - Transcript show:'timeout - output size is: '; showCR:newSize. + Logger info:'timeout - output size is: %1' with:newSize. (newSize between:1 and:1000) ifTrue:[ - Transcript show:'output is: '; showCR:collectedOutput contents. + Logger info:'output is: "%1"' with:collectedOutput contents. ]. ]. @@ -759,30 +783,33 @@ |n sensor| readerDelay notNil ifTrue:[ Delay waitForSeconds:readerDelay]. - outStream isNil ifTrue:[^ self]. - outStream readWait. + outStream isNil ifTrue:[ + Delay waitForSeconds:0.1 + ] ifFalse:[ + outStream readWait. - (pluggableCheckBeforeReadAction isNil - or:[pluggableCheckBeforeReadAction value]) ifTrue:[ - n := self readAnyAvailableData. - n == 0 ifTrue:[ - "/ Windows IPC has a bug - it always - "/ returns 0 (when the command is idle) - "/ and says it's at the end (sigh) + (pluggableCheckBeforeReadAction isNil + or:[pluggableCheckBeforeReadAction value]) ifTrue:[ + n := self readAnyAvailableData. + n == 0 ifTrue:[ + "/ Windows IPC has a bug - it always + "/ returns 0 (when the command is idle) + "/ and says it's at the end (sigh) - OperatingSystem isMSWINDOWSlike ifTrue:[ - Delay waitForSeconds:0.1 - ] ifFalse:[ - outStream atEnd ifTrue:[ - outStream close. outStream := nil. - inStream close. inStream := nil. - Processor activeProcess terminate. + OperatingSystem isMSWINDOWSlike ifTrue:[ + Delay waitForSeconds:0.1 ] ifFalse:[ - "/ this should not happen. + outStream atEnd ifTrue:[ + outStream close. outStream := nil. + inStream close. inStream := nil. + Processor activeProcess terminate. + ] ifFalse:[ + "/ this should not happen. - Delay waitForSeconds:0.1 - ] - ]. + Delay waitForSeconds:0.1 + ] + ]. + ] ] ] ] @@ -794,6 +821,7 @@ "Start a reader process, which looks for the commands output, and sends me #processInput:n: events whenever something arrives." + Logger info:'start reader'. readerProcess isNil ifTrue:[ readerProcess := [ [ @@ -818,6 +846,7 @@ |p| + Logger info:'stop reader'. (p := readerProcess) notNil ifTrue:[ readerProcess := nil. p terminate.