You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
426 lines
14 KiB
426 lines
14 KiB
# -*- tcl -*- |
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt |
|
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm |
|
# |
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# (C) 2025 |
|
# |
|
# @@ Meta Begin |
|
# Application punk::winshell 999999.0a1.0 |
|
# Meta platform tcl |
|
# Meta license <unspecified> |
|
# @@ Meta End |
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# doctools header |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
#*** !doctools |
|
#[manpage_begin shellspy_module_punk::winshell 0 999999.0a1.0] |
|
#[copyright "2025"] |
|
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] |
|
#[moddesc {-}] [comment {-- Description at end of page heading --}] |
|
#[require punk::winshell] |
|
#[keywords module] |
|
#[description] |
|
#[para] - |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
#*** !doctools |
|
#[section Overview] |
|
#[para] overview of punk::winshell |
|
#[subsection Concepts] |
|
#[para] - |
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
## Requirements |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
#*** !doctools |
|
#[subsection dependencies] |
|
#[para] packages used by punk::winshell |
|
#[list_begin itemized] |
|
|
|
package require Tcl 8.6- |
|
#*** !doctools |
|
#[item] [package {Tcl 8.6}] |
|
|
|
# #package require frobz |
|
# #*** !doctools |
|
# #[item] [package {frobz}] |
|
|
|
#*** !doctools |
|
#[list_end] |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
#*** !doctools |
|
#[section API] |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# oo::class namespace |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
#tcl::namespace::eval punk::winshell::class { |
|
#*** !doctools |
|
#[subsection {Namespace punk::winshell::class}] |
|
#[para] class definitions |
|
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} { |
|
#*** !doctools |
|
#[list_begin enumerated] |
|
|
|
# oo::class create interface_sample1 { |
|
# #*** !doctools |
|
# #[enum] CLASS [class interface_sample1] |
|
# #[list_begin definitions] |
|
|
|
# method test {arg1} { |
|
# #*** !doctools |
|
# #[call class::interface_sample1 [method test] [arg arg1]] |
|
# #[para] test method |
|
# puts "test: $arg1" |
|
# } |
|
|
|
# #*** !doctools |
|
# #[list_end] [comment {-- end definitions interface_sample1}] |
|
# } |
|
|
|
#*** !doctools |
|
#[list_end] [comment {--- end class enumeration ---}] |
|
#} |
|
#} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# Base namespace |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
tcl::namespace::eval punk::winshell { |
|
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase |
|
#variable xyz |
|
|
|
#*** !doctools |
|
#[subsection {Namespace punk::winshell}] |
|
#[para] Core API functions for punk::winshell |
|
#[list_begin definitions] |
|
|
|
|
|
#The windows api we need here is createPseudoConsole et al. |
|
|
|
variable autoshellid 0 |
|
variable shellinfo [dict create] |
|
|
|
#test of exec and named pipes. |
|
#we don't get a console |
|
proc cmdexec {{id ""}} { |
|
variable autoshellid |
|
variable shellinfo |
|
package require twapi |
|
set pipebase {\\.\pipe\punkwinshell} |
|
if {$id eq ""} { |
|
incr autoshellid |
|
set shellid $autoshellid |
|
} else { |
|
set shellid $id |
|
} |
|
|
|
set pipename_stdin $pipebase$shellid-stdin |
|
set pipename_stdout $pipebase$shellid-stdout |
|
set pipename_stderr $pipebase$shellid-stderr |
|
#swapped thisend/child - labels now wrong - todo - relabel or swap back? |
|
set h_stdin [twapi::namedpipe_server $pipename_stdin] ;#handle for child process redirection |
|
set p_stdin [twapi::namedpipe_client $pipename_stdin] ;#this end |
|
set h_stdout [twapi::namedpipe_server $pipename_stdout] ;#handle for child process redirection |
|
set p_stdout [twapi::namedpipe_client $pipename_stdout] ;#this end |
|
chan configure $p_stdout -blocking 0 |
|
set h_stderr [twapi::namedpipe_server $pipename_stderr] ;#handle for child process redirection |
|
set p_stderr [twapi::namedpipe_client $pipename_stderr] ;#this end |
|
chan configure $p_stderr -blocking 0 |
|
|
|
set pid [exec cmd.exe /k >@$p_stdout 2>@$p_stderr <@$p_stdin &] |
|
dict set shellinfo $shellid id $shellid |
|
dict set shellinfo $shellid pid $pid |
|
dict set shellinfo $shellid stdin $h_stdin |
|
dict set shellinfo $shellid stdout $h_stdout |
|
dict set shellinfo $shellid stderr $h_stderr |
|
|
|
return [dict get $shellinfo $shellid] |
|
} |
|
variable ack 0 |
|
proc handle_out {chan args} { |
|
variable ack |
|
#if {[catch { |
|
# if {$ack} { |
|
# punk::console::move_emit_return 3 79 "\\" |
|
# set ack 0 |
|
# } else { |
|
# punk::console::move_emit_return 3 79 / |
|
# set ack 1 |
|
# } |
|
#} errM]} { |
|
# puts "err on move_emit_return" |
|
#} |
|
puts -nonewline stdout [punk::ansi::ansistring VIEW [read $chan]] |
|
} |
|
proc handle_err {chan args} { |
|
variable ack |
|
#if {$ack} { |
|
# punk::console::move_emit_return 3 79 - |
|
# set ack 0 |
|
#} else { |
|
# punk::console::move_emit_return 3 79 | |
|
# set ack 1 |
|
#} |
|
puts -nonewline stderr [read $chan] |
|
} |
|
|
|
proc cmdtest {{id ""}} { |
|
set cinfo [cmdexec $id] |
|
set o [dict get $cinfo stdout] |
|
chan conf $o -buffering none -blocking 0 |
|
set e [dict get $cinfo stderr] |
|
chan conf $e -buffering none -blocking 0 |
|
chan event $o readable [list ::punk::winshell::handle_out $o] |
|
chan event $e readable [list ::punk::winshell::handle_err $e] |
|
return $cinfo |
|
} |
|
|
|
#test with twapi create_process |
|
proc cmdcreate {{id ""}} { |
|
variable autoshellid |
|
variable shellinfo |
|
package require twapi |
|
set pipebase {\\.\pipe\punkwinshell} |
|
if {$id eq ""} { |
|
incr autoshellid |
|
set shellid $autoshellid |
|
} else { |
|
set shellid $id |
|
} |
|
|
|
|
|
#Method 1) - using windows named pipes |
|
set pipename_stdin $pipebase$shellid-stdin |
|
set pipename_stdout $pipebase$shellid-stdout |
|
set pipename_stderr $pipebase$shellid-stderr |
|
#set h_stdin [twapi::namedpipe_server $pipename_stdin] ;#handle for child process redirection - child to read |
|
#set p_stdin [twapi::namedpipe_client $pipename_stdin] ;#this end for writing |
|
|
|
#set h_stdout [twapi::namedpipe_server $pipename_stdout] ;#handle for child process redirection - child to write |
|
#set p_stdout [twapi::namedpipe_client $pipename_stdout] ;#this end for reading |
|
#set h_stderr [twapi::namedpipe_server $pipename_stderr] ;#handle for child process redirection - child to write |
|
#set p_stderr [twapi::namedpipe_client $pipename_stderr] ;#this end for reading |
|
|
|
#test |
|
set p_stdout [twapi::namedpipe_server $pipename_stdout] ;#handle for child process redirection - child to write |
|
set p_stdin "" |
|
set p_stderr "" |
|
chan configure $p_stdout -blocking 0 |
|
|
|
|
|
#Method 2) - using tcl's 'chan pipe' which creates OS level channels |
|
#chan pipe returns rd wr channels in that order |
|
#lassign [chan pipe] h_stdin p_stdin |
|
#lassign [chan pipe] p_stdout h_stdout |
|
#lassign [chan pipe] p_stderr h_stderr |
|
|
|
#chan configure $p_stdout -blocking 0 |
|
#chan configure $p_stderr -blocking 0 |
|
|
|
#set cmd {C:\Users\sleek\scoop\apps\windows-terminal\current\WindowsTerminal.exe} ;#doesn't work? |
|
#set cmd "[auto_execok cmd.exe] /k" |
|
#set cmd "[auto_execok powershell] -nop" |
|
#set cmd "[auto_execok tclsh]" |
|
set cmd "[auto_execok tclsh90]" |
|
|
|
set flagdict [dict create\ |
|
-cmdline "$cmd"\ |
|
-newconsole 1\ |
|
-inherithandles 0\ |
|
-background blue\ |
|
-title "punk::winshell $shellid" |
|
] |
|
|
|
#dict set flagdict -stdchannels [list $h_stdin $h_stdout $h_stderr] |
|
|
|
set program "" |
|
lassign [twapi::create_process $program {*}$flagdict] pid tid |
|
|
|
|
|
puts stdout "launched with pid:$pid tid:$tid" |
|
#set pid [exec cmd.exe /k >@$h_stdout 2>@$h_stderr <@$h_stdin &] |
|
|
|
dict set shellinfo $shellid id $shellid |
|
dict set shellinfo $shellid pid $pid |
|
dict set shellinfo $shellid type "create_process" |
|
dict set shellinfo $shellid stdin $p_stdin |
|
dict set shellinfo $shellid stdout $p_stdout |
|
dict set shellinfo $shellid stderr $p_stderr |
|
|
|
return [dict get $shellinfo $shellid] |
|
} |
|
proc cmdexit {shellid} { |
|
variable shellinfo |
|
set info [dict get $shellinfo $shellid] |
|
switch -- [dict get $info type] { |
|
"create_process" { |
|
set exitresult [twapi::end_process [dict get $info pid]] |
|
} |
|
"exec" { |
|
puts stderr "todo.." |
|
puts stderr "manually kill exec process [dict get $info pid]" |
|
set exitresult 0 |
|
} |
|
} |
|
return [dict create exitresult $exitresult] |
|
} |
|
|
|
proc cmdkill {shellid} { |
|
variable shellinfo |
|
set info [dict get $shellinfo $shellid] |
|
set pid [dict get $info pid] |
|
set killcmd [list taskkill /PID $pid] |
|
|
|
if {[catch { |
|
exec {*}$killcmd |
|
} errMsg]} { |
|
puts stderr "$killcmd returned an error:" |
|
puts stderr $errMsg |
|
#if {!$forcekill} { |
|
# puts stderr "(try 'kill -9 $pid' ??)" |
|
#} |
|
|
|
#review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms? |
|
if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} { |
|
lappend failed_kits [list reason "could not kill running process for shellid $shellid (using '$killcmd')"] |
|
continue |
|
} else { |
|
puts stderr "" |
|
} |
|
} else { |
|
puts stderr "$killcmd ran without error" |
|
incr count_killed |
|
} |
|
|
|
} |
|
|
|
proc shellinfo {} { |
|
variable shellinfo |
|
return $shellinfo |
|
} |
|
proc cmdinfo {{id ""}} { |
|
variable autoshellid |
|
variable shellinfo |
|
if {$id eq ""} { |
|
#last created |
|
set shellid $autoshellid |
|
} else { |
|
set shellid $id |
|
} |
|
set info [dict get $shellinfo $shellid] |
|
set pid [dict get $info pid] |
|
|
|
catch { |
|
set statusresult [tcl::process status $pid] |
|
dict set info status $statusresult |
|
} |
|
|
|
set cmdline [twapi::get_process_commandline $pid] |
|
dict set info cmdline $cmdline |
|
return [showdict $info] |
|
} |
|
|
|
#quick n dirty - status of last (or identified) winshell |
|
proc cmdstat {{id ""}} { |
|
variable autoshellid |
|
variable shellinfo |
|
if {$id eq ""} { |
|
#last created |
|
set shellid $autoshellid |
|
} else { |
|
set shellid $id |
|
} |
|
set pid [dict get $shellinfo $shellid pid] |
|
set statusresult "" |
|
catch { |
|
#not in 8.6? |
|
set statusresult [tcl::process status $pid] |
|
} |
|
return [dict create id $shellid status $statusresult] |
|
} |
|
|
|
|
|
|
|
#proc sample1 {p1 n args} { |
|
# #*** !doctools |
|
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]] |
|
# #[para]Description of sample1 |
|
# #[para] Arguments: |
|
# # [list_begin arguments] |
|
# # [arg_def tring p1] A description of string argument p1. |
|
# # [arg_def integer n] A description of integer argument n. |
|
# # [list_end] |
|
# return "ok" |
|
#} |
|
|
|
|
|
|
|
|
|
#*** !doctools |
|
#[list_end] [comment {--- end definitions namespace punk::winshell ---}] |
|
} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# Secondary API namespace |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
tcl::namespace::eval punk::winshell::lib { |
|
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase |
|
tcl::namespace::path [tcl::namespace::parent] |
|
#*** !doctools |
|
#[subsection {Namespace punk::winshell::lib}] |
|
#[para] Secondary functions that are part of the API |
|
#[list_begin definitions] |
|
|
|
#proc utility1 {p1 args} { |
|
# #*** !doctools |
|
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] |
|
# #[para]Description of utility1 |
|
# return 1 |
|
#} |
|
|
|
|
|
|
|
#*** !doctools |
|
#[list_end] [comment {--- end definitions namespace punk::winshell::lib ---}] |
|
} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
#*** !doctools |
|
#[section Internal] |
|
#tcl::namespace::eval punk::winshell::system { |
|
#*** !doctools |
|
#[subsection {Namespace punk::winshell::system}] |
|
#[para] Internal functions that are not part of the API |
|
|
|
|
|
|
|
#} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
## Ready |
|
package provide punk::winshell [tcl::namespace::eval punk::winshell { |
|
variable pkg punk::winshell |
|
variable version |
|
set version 999999.0a1.0 |
|
}] |
|
return |
|
|
|
#*** !doctools |
|
#[manpage_end] |
|
|
|
|