Julian Noble
2 years ago
commit
b1af07bd5a
1928 changed files with 663075 additions and 0 deletions
@ -0,0 +1,356 @@
|
||||
namespace eval shellspy::callbacks { |
||||
package require shellfilter |
||||
|
||||
|
||||
#each member of args - ist not itself a list - and cannot be treated as one. |
||||
#things like [concat {*}args] will generall break things further down the line |
||||
proc cmdshellb {args} { |
||||
shellfilter::log::open callback_cmdb {-syslog 127.0.0.1:514} |
||||
shellfilter::log::write callback_cmdb "cmdshellb got [llength $args] arguments" |
||||
shellfilter::log::write callback_cmdb "cmdshellb got '$args'" |
||||
|
||||
if {[lindex $args 0] eq "cmdb"} { |
||||
set curlyparts [lrange $args 1 end] |
||||
shellfilter::log::write callback_cmdb "cmdshellb curlyparts '$curlyparts'" |
||||
#we lose grouping info by joining like this.. |
||||
#set tail [string trim [join $curlyparts " "]] |
||||
set tailinfo [shellfilter::list_element_info $curlyparts] |
||||
|
||||
shellfilter::log::write callback_cmdb "cmdshellb tailinfo '$tailinfo'" |
||||
#e.g |
||||
#% set y {{"c:/test/etc"} true {'blah1'} {'blah 2'}} |
||||
#% lappend y \\\\\\ |
||||
#{"c:/test/etc"} true {'blah1'} {'blah 2'} \\\\\\ |
||||
#% foreach i [shellfilter::list_element_info $y] {puts $i} |
||||
# 0 |
||||
# wouldbrace 1 wouldescape 0 has_braces 0 has_inner_braces 0 apparentwrap doublequotes head_tail_chars {{"} {"}} head_tail_names {dq dq} len 13 difflen 2 |
||||
# 1 |
||||
# wouldbrace 0 wouldescape 0 has_braces 0 has_inner_braces 0 apparentwrap not-determined head_tail_chars {t e} head_tail_names {t e} len 4 difflen 0 |
||||
# 2 |
||||
# wouldbrace 0 wouldescape 0 has_braces 0 has_inner_braces 0 apparentwrap singlequotes head_tail_chars {' '} head_tail_names {sqote sqote} len 7 difflen 0 |
||||
# 3 |
||||
# wouldbrace 1 wouldescape 0 has_braces 0 has_inner_braces 0 apparentwrap singlequotes head_tail_chars {' '} head_tail_names {sqote sqote} len 8 difflen 2 |
||||
# 4 |
||||
# wouldbrace 0 wouldescape 1 has_braces 0 has_inner_braces 0 apparentwrap not-determined head_tail_chars {\\ \\} head_tail_names {\\ \\} len 3 difflen 3 |
||||
|
||||
|
||||
#sample arglist - 7 items |
||||
#((c:\lua\luajit.exe |
||||
#C:\Users\sleek\vimfiles\plugged\vim-flog/lua/flog/graph_bin.lua |
||||
#__START |
||||
#true |
||||
#git -C C:/repo/3p/ansi-to-html --literal-pathspecs log --parents --topo-order --no-color --pretty=format:__START\%n\%h\%n\%p\%n\%D\%n\%ad\ \[\%h\]\ \{\%an\}\%d\ \%s --date=iso --no-merges --max-count=2000 -- ) |
||||
#> |
||||
#C:\Users\sleek\AppData\Local\Temp\VRR6A67.tmp) |
||||
|
||||
|
||||
#complex method.. lists at levels.. |
||||
set taillist [list] |
||||
set level 0 ;#bracket depth.. incr each opening bracket "(" |
||||
set leveldict [dict create] |
||||
dict set leveldict 0 [list] |
||||
|
||||
#simpler method.. string |
||||
set output "" |
||||
dict for {idx info} $tailinfo { |
||||
set item [lindex $curlyparts $idx] |
||||
set itemlen [string length $item] |
||||
if {[dict get $info apparentwrap] eq "brackets"} { |
||||
set chars [split $item ""] |
||||
set opening [lsearch -all $chars "("] |
||||
set closing [lsearch -all $chars ")"] |
||||
if {[llength $opening] == [llength $closing]} { |
||||
#dict lappend leveldict 0 $item ;#element individually wrapped in brackets and balanced, pass through |
||||
append output "$item " |
||||
} else { |
||||
#robustness warning: we examine outer brackets only, and are ignoring that things like {((a)(b)} {((a)(b} |
||||
# are composed of multiple elements for now .. as there should have been a space between the (a) & (b) elements anyway, |
||||
# in which case we would only see things like {((a)} or {(a} |
||||
set ltrimmed [string trimleft $item "("] |
||||
set countleft [expr {$itemlen - [string length $ltrimmed]}] |
||||
|
||||
set rtrimmed [string trimright $item ")"] |
||||
set countright [expr {$itemlen - [string length $rtrimmed]}] |
||||
|
||||
#simpler method.. |
||||
append output "$item " |
||||
|
||||
} |
||||
} else { |
||||
set lcharname [lindex [dict get $info head_tail_names] 0] |
||||
set rcharname [lindex [dict get $info head_tail_names] 1] |
||||
|
||||
if {$lcharname eq "lbracket"} { |
||||
set ltrimmed [string trimleft $item "("] |
||||
set countleft [expr {$itemlen - [string length $ltrimmed]}] |
||||
set braces [string repeat "(" $countleft] |
||||
|
||||
set testlist [list] |
||||
lappend testlist $ltrimmed |
||||
set testinfo [shellfilter::list_element_info $testlist] |
||||
set testelement [dict get $testinfo 0] |
||||
if {[dict get $testelement wouldbrace]} { |
||||
#append output "${braces}\"$ltrimmed\" " |
||||
append output "${braces} $ltrimmed " |
||||
} else { |
||||
append output "${braces} $ltrimmed " |
||||
} |
||||
|
||||
} elseif {$rcharname eq "rbracket"} { |
||||
set rtrimmed [string trimright $item ")" ] |
||||
set countright [expr {$itemlen - [string length $rtrimmed]}] |
||||
set braces [string repeat ")" $countright] |
||||
set testlist [list] |
||||
lappend testlist $rtrimmed |
||||
set testinfo [shellfilter::list_element_info $testlist] |
||||
set testelement [dict get $testinfo 0] |
||||
if {[dict get $testelement wouldbrace]} { |
||||
#append output "\"$rtrimmed\"${braces} " |
||||
if {[string first " " $rtrimmed] > 0} { |
||||
append output "\"$rtrimmed\" ${braces} " |
||||
} else { |
||||
append output "$rtrimmed ${braces} " |
||||
} |
||||
} else { |
||||
append output "${rtrimmed} ${braces} " |
||||
} |
||||
|
||||
|
||||
} else { |
||||
set testlist [list] |
||||
lappend testlist $item |
||||
set testinfo [shellfilter::list_element_info $testlist] |
||||
set testelement [dict get $testinfo 0] |
||||
if {[dict get $testelement wouldbrace]} { |
||||
#append output "\"$item\" " |
||||
if {[string first " " $item] > 0} { |
||||
append output "\"$item\" " |
||||
} else { |
||||
append output "$item " |
||||
} |
||||
} else { |
||||
append output "$item " |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
shellfilter::log::write callback_cmdb "cmdshellb about to parse_cmd_brackets '$output'" |
||||
#$output now has quoted all items that 'wouldbrace' |
||||
set curly_list [shellfilter::parse_cmd_brackets $output] |
||||
if {[llength $curly_list] == 1} { |
||||
#we expect the whole set of arguments was wrapped in a set of brackets |
||||
set curly_items [lindex $curly_list 0] |
||||
} else { |
||||
#unexpected.. root level of string had multiple bracketed sections |
||||
#try using the curly_list directly warn? |
||||
set curly_items $curly_list |
||||
} |
||||
#e.g |
||||
# ((c:\lua\luajit.exe -v) > C:\Users\sleek\AppData\Local\Temp\V7NCBF.tmp) |
||||
#=> |
||||
# {{{c:\lua\luajit.exe} -v} > {C:\Users\sleek\AppData\Local\Temp\V7NCBF.tmp}} |
||||
|
||||
#what is the proper way to flatten? |
||||
|
||||
set comspec [lindex $curly_items 0] |
||||
if {[llength $comspec] >1} { |
||||
set commandlist [concat $comspec [lrange $curly_items 1 end]] |
||||
} else { |
||||
set commandlist $curly_items |
||||
} |
||||
|
||||
set commandlist [floghack_singlearg callback_cmdb {*}$commandlist] |
||||
return $commandlist |
||||
} else { |
||||
shellfilter::log::write callback_cmdb "first arg: [lindex $args 0] vs 'cmdb'" |
||||
error "first arg: [lindex $args 0] vs 'cmdb'" |
||||
#return $args |
||||
} |
||||
} |
||||
proc cmdshell {args} { |
||||
if {[catch { |
||||
set args [floghack_singlearg callback_cmdshell {*}$args] |
||||
} errMsg]} { |
||||
error "FLOGHACK callback_cmdshell error $errMsg" |
||||
} |
||||
|
||||
|
||||
#set args [concat [lindex $args 0] [lrange $args 1 end]] |
||||
return $args |
||||
} |
||||
proc cmdshelluc {args} { |
||||
if {[catch { |
||||
set args [floghack_singlearg callback_cmdshelluc {*}$args] |
||||
} errMsg]} { |
||||
error "FLOGHACK callback_cmdshelluc error $errMsg" |
||||
} |
||||
|
||||
#set args [concat [lindex $args 0] [lrange $args 1 end]] |
||||
return $args |
||||
} |
||||
|
||||
proc powershell {args} { |
||||
if {[catch { |
||||
set args [floghack "callback_powershell" {*}$args] |
||||
} errMsg]} { |
||||
error "FLOGHACK callback_powershell error $errMsg" |
||||
} |
||||
return $args |
||||
|
||||
} |
||||
proc raw {args} { |
||||
if {[catch { |
||||
set args [floghack_singlearg "callback_raw" {*}$args] |
||||
} errMsg]} { |
||||
error "FLOGHACK callback_raw error $errMsg" |
||||
} |
||||
#set args [concat [split [lindex $args 0]] [lrange $args 1 end]] ;#definitely bad! |
||||
|
||||
|
||||
return $args |
||||
} |
||||
|
||||
#hack for c: drive - extend as necessary |
||||
#todo - customize! |
||||
# various hacks may be necessary for home dirs temp files etc! |
||||
proc sh {args} { |
||||
if {[catch { |
||||
set args [floghack callback_sh {*}$args] |
||||
} errMsg]} { |
||||
error "FLOGHACK callback_sh error $errMsg" |
||||
} |
||||
|
||||
|
||||
|
||||
set final [list] |
||||
foreach a $args { |
||||
if {[string match -nocase {*c:/*} $a]} { |
||||
set a [string map [list {c:/} {/c/}] $a] |
||||
lappend final $a |
||||
} elseif {[string match -nocase {*c:\\*} $a]} { |
||||
set a [string map [list \\ / ] $a] |
||||
set a [string map [list {c:/} {/c/} {C:/} {/C/}] $a] |
||||
lappend final $a |
||||
} else { |
||||
lappend final $a |
||||
} |
||||
} |
||||
return $final |
||||
} |
||||
proc wsl {args} { |
||||
set args [floghack_singlearg callback_wsl {*}$args] |
||||
#wsl bash-style /mnt/c paths to be convertd |
||||
set args [convert_mnt_win {*}$args] |
||||
|
||||
#review - seems bad |
||||
#flatten first element which arrives wrapped |
||||
#set args [concat [lindex $args 0] [lrange $args 1 end]] |
||||
|
||||
|
||||
return $args |
||||
} |
||||
proc bash {args} { |
||||
set args [floghack callback_bash {*}$args] |
||||
return [convert_mnt_win {*}$args] |
||||
} |
||||
|
||||
|
||||
|
||||
#helper functions |
||||
proc convert_mnt_win {args} { |
||||
set final [list] |
||||
foreach a $args { |
||||
if {[string match -nocase {*c:\\*} $a]} { |
||||
set a [string map [list \\ / ] $a] |
||||
} |
||||
#bash seems to be strict about lowercase /mnt/c |
||||
if {[string match -nocase {*c:/*} $a]} { |
||||
set a [string map [list {c:/} {/mnt/c/} {C:/} {/mnt/c/}] $a] |
||||
} |
||||
lappend final $a |
||||
} |
||||
shellfilter::log::write callback_convert_mnt_win "convert_mnt_win commandlist '$final'" |
||||
return $final |
||||
} |
||||
|
||||
#when we get the git command and args together as one element in the commandlist |
||||
proc floghack_singlearg {logtag args} { |
||||
#return $args |
||||
shellfilter::log::write $logtag "floghack_singlearg got $logtag '$args'" |
||||
set newargs [list] |
||||
foreach a $args { |
||||
if {[string match "*pretty=format:__START*" $a]} { |
||||
set a [string map [list "pretty=format:__" "pretty=format:\"__"] $a] |
||||
set a [string map [list " --date=" "\" --date="] $a] |
||||
set a [string map [list \\ ""] $a] ;# a bad idea? |
||||
lappend newargs $a |
||||
} else { |
||||
lappend newargs $a |
||||
} |
||||
} |
||||
shellfilter::log::write $logtag "floghack_singlearg hacked commandlist '$newargs'" |
||||
return $newargs |
||||
} |
||||
|
||||
|
||||
proc floghack {logtag args} { |
||||
|
||||
#return $args |
||||
shellfilter::log::write $logtag "floghack got [llength $args] args: '$args'" |
||||
set indent " " |
||||
set i 0 |
||||
foreach a $args { |
||||
shellfilter::log::write $logtag "floghack ${indent}$i $a" |
||||
incr i |
||||
} |
||||
|
||||
#Flog/luajit hack |
||||
#wrong way |
||||
#if {[string first "pretty=format:__START" $args] > 0} { |
||||
# set args [string map [list "pretty=format:" "pretty=format:'" " --date=" "' --date=" ] $args] |
||||
#} |
||||
set newargs [list] |
||||
|
||||
set pretty "" |
||||
set pstart [lsearch $args "--pretty=format:__START*"] |
||||
set pafter [lsearch $args "--date=*"] |
||||
if {$pstart > 0} { |
||||
set newargs [lrange $args 0 $pstart-1] |
||||
set parts [lrange $args $pstart $pafter-1] |
||||
set i 1 |
||||
foreach p $parts { |
||||
if {$i == 1} { |
||||
set pretty [string map [list "format:__" "format:\"__"] $p] |
||||
#set pretty $p |
||||
} else { |
||||
append pretty " $p" |
||||
} |
||||
if {$i == [llength $parts]} { |
||||
append pretty "\"" |
||||
} |
||||
incr i |
||||
} |
||||
set pretty [string map [list \\ ""] $pretty] |
||||
lappend newargs $pretty |
||||
#lappend newargs "--pretty=format:%h" |
||||
set newargs [concat $newargs [lrange $args $pafter end]] ;#concat of 2 lists.. should be ok |
||||
} else { |
||||
set newargs $args |
||||
} |
||||
|
||||
|
||||
shellfilter::log::write $logtag "floghack hacked commandlist '$newargs'" |
||||
|
||||
|
||||
return $newargs |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} |
@ -0,0 +1,51 @@
|
||||
namespace eval shellspy::parameters { |
||||
proc cmdshellb {paramdict} { |
||||
return [commonset params_cmdshellb $paramdict] |
||||
} |
||||
|
||||
|
||||
proc cmdshell {paramdict} { |
||||
return [commonset params_cmdshell $paramdict] |
||||
} |
||||
|
||||
proc powershell {paramdict} { |
||||
return [commonset params_powershell $paramdict] |
||||
} |
||||
|
||||
proc raw {paramdict} { |
||||
return [commonset params_raw $paramdict] |
||||
} |
||||
|
||||
proc wsl {paramdict} { |
||||
return [commonset params_wsl $paramdict] |
||||
} |
||||
proc bash {paramdict} { |
||||
return [commonset params_bash $paramdict] |
||||
} |
||||
|
||||
proc sh {paramdict} { |
||||
return [commonset params_sh $paramdict] |
||||
} |
||||
|
||||
proc commonset {logtag paramdict} { |
||||
#timeout for launched process |
||||
dict set paramdict -timeout 60000 |
||||
#prefix for each stdout line - for debugging if this output being mixed with some other |
||||
dict set paramdict -outprefix "" |
||||
#prefix for each stderr line - also usually best left blank |
||||
dict set paramdict -errprefix "" |
||||
#put extra info to the log outputs (by default goes to syslog 127.0.0.1 514) |
||||
dict set paramdict -debug 0 |
||||
dict set paramdict -outbuffering none |
||||
dict set paramdict -inbuffering none |
||||
#if at tail end of commandline there is a > or >> redirection to a file that contains 'temp' |
||||
# make a '_copy' version e.g for something/temp/blah.tmp |
||||
# create something/temp/blah_copy.tmp |
||||
# this is for pipelines where some other process deletes the original temp file but you want a copy |
||||
# to review/debug. |
||||
dict set paramdict -copytempfile 1 |
||||
|
||||
shellfilter::log::write $logtag "base parameters: $paramdict" |
||||
return $paramdict |
||||
} |
||||
} |
@ -0,0 +1,54 @@
|
||||
|
||||
if {$::argc == 1} { |
||||
set persec $::argv |
||||
} else { |
||||
set persec 1 |
||||
} |
||||
if {$persec > 1000} { |
||||
puts stderr "WARNING: (>1000) sub millisecond scheduling not available - will go full speed" |
||||
flush stderr |
||||
after 500 |
||||
} |
||||
#--- confg --- |
||||
set newline_every_x_seconds 5 |
||||
#--- |
||||
chan configure stdout -blocking 1 -buffering none |
||||
set counter 0 |
||||
set ms [expr {1000 / $persec}] |
||||
set nl_every [expr {$persec * $newline_every_x_seconds}] |
||||
|
||||
proc schedule {} { |
||||
after idle [list after 0 ::emit] |
||||
tailcall after $::ms ::schedule |
||||
} |
||||
|
||||
proc emit {} { |
||||
upvar ::counter c |
||||
puts -nonewline "\x1b\[1000D$c" |
||||
|
||||
#if {($c > 1) && (($c % $::nl_every) == 0)} { |
||||
# puts stdout $c |
||||
# flush stdout |
||||
#} else { |
||||
# puts -nonewline "\x1b\[1000D$c" |
||||
#} |
||||
#flush stdout |
||||
incr c |
||||
} |
||||
chan configure stdin -blocking 0 -buffering none |
||||
chan event stdin readable [list apply {{chan} { |
||||
set chunk [chan read $chan] |
||||
if {[string length $chunk]} { |
||||
if {[string match "q*" [string tolower $chunk]]} { |
||||
set ::forever 0 |
||||
} |
||||
} |
||||
if {[chan eof $chan]} { |
||||
chan event $chan readable {} |
||||
} |
||||
}} stdin] |
||||
|
||||
schedule |
||||
vwait ::forever |
||||
|
||||
puts "-done-" |
@ -0,0 +1,32 @@
|
||||
puts -nonewline stdout "info script\r\n" |
||||
puts stdout "[info script]" |
||||
puts stdout "::argc" |
||||
puts stdout $::argc |
||||
puts stdout "::argv" |
||||
puts stdout "$::argv" |
||||
puts stdout "NS" |
||||
puts stdout "[namespace current]" |
||||
|
||||
if {[info exists ::test]} { |
||||
puts stdout "::test has existing value" |
||||
puts stdout "$::test" |
||||
} |
||||
|
||||
puts stdout "setting ::test to showargs-ran" |
||||
set ::test "showargs-ran" |
||||
puts stdout "doing basic check of stdin for data" |
||||
chan configure stdin -blocking 0 |
||||
set indata "" |
||||
if {![chan eof stdin]} { |
||||
#set indata [read stdin] |
||||
} |
||||
if {[string length $indata]} { |
||||
puts stdout "read from stdin:" |
||||
puts stdout $indata |
||||
} else { |
||||
puts stdout "-no stdin data read-" |
||||
} |
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,77 @@
|
||||
|
||||
if {$::argc >= 1} { |
||||
set persec [lindex $::argv 0] |
||||
} else { |
||||
set persec 1 |
||||
} |
||||
if {$::argc == 2} { |
||||
set what [lindex $::argv 1] |
||||
} else { |
||||
set what "." |
||||
} |
||||
|
||||
if {$persec > 1000} { |
||||
puts stderr "WARNING: (>1000) sub millisecond scheduling not available - will go full speed" |
||||
flush stderr |
||||
after 500 |
||||
} |
||||
#--- confg --- |
||||
set newline_every_x_seconds 5 |
||||
#--- |
||||
chan configure stdout -blocking 1 -buffering none |
||||
set counter 0 |
||||
set ms [expr {1000 / $persec}] |
||||
set nl_every [expr {$persec * $newline_every_x_seconds}] |
||||
|
||||
proc schedule {} { |
||||
if {$::forever_stdout_per_second} { |
||||
after idle [list after 0 ::emit] |
||||
tailcall after $::ms ::schedule |
||||
} else { |
||||
after idle [list ::the_end] |
||||
} |
||||
} |
||||
|
||||
set ::forever_stdout_per_second 1 |
||||
|
||||
proc the_end {} { |
||||
puts "-done-" |
||||
flush stdout |
||||
set ::done_stdout_per_second 1 |
||||
} |
||||
|
||||
proc emit {} { |
||||
upvar ::counter c |
||||
if {($c > 1) && (($c % $::nl_every) == 0)} { |
||||
puts -nonewline stdout " " |
||||
flush stdout |
||||
puts stderr $c |
||||
flush stderr |
||||
} else { |
||||
puts -nonewline stdout $::what |
||||
} |
||||
#flush stdout |
||||
incr c |
||||
} |
||||
chan configure stdin -blocking 0 -buffering none |
||||
chan event stdin readable [list apply {{chan} { |
||||
set chunk [chan read $chan] |
||||
if {[string length $chunk]} { |
||||
if {[string match "*q*" [string tolower $chunk]]} { |
||||
set ::forever_stdout_per_second 0 |
||||
chan event $chan readable {} |
||||
puts stdout "cancelling" |
||||
} |
||||
} |
||||
if {[chan eof $chan]} { |
||||
chan event $chan readable {} |
||||
} |
||||
}} stdin] |
||||
|
||||
schedule |
||||
vwait ::forever_stdout_per_second |
||||
|
||||
vwait ::done_stdout_per_second |
||||
|
||||
|
||||
|
@ -0,0 +1,118 @@
|
||||
|
||||
|
||||
if {[lsearch $::argv -k] >= 0} { |
||||
set forcekill 1 |
||||
} else { |
||||
set forcekill 0 |
||||
} |
||||
puts stdout "::argv $::argv" |
||||
|
||||
set dirname [file normalize [file dirname [info script]]] |
||||
|
||||
if {![file exists $dirname/punk86.vfs]} { |
||||
puts stderr "missing $dirname/punk86.vfs" |
||||
exit 1 |
||||
} |
||||
if {[file exists $dirname/punk86]} { |
||||
puts stderr "deleting existing $dirname/punk86" |
||||
file delete $dirname/punk86 |
||||
} |
||||
|
||||
puts stdout "building with sdx.." |
||||
if {[catch { |
||||
exec sdx wrap punk86 -runtime tclkit86bi.exe -verbose |
||||
} result]} { |
||||
puts stderr "sdx wrap punk86 -runtime tclkit86bi.exe -verbose failed with msg: $result" |
||||
} else { |
||||
puts stdout "ok - finished sdx" |
||||
set separator [string repeat = 40] |
||||
puts stdout $separator |
||||
puts stdout $result |
||||
puts stdout $separator |
||||
} |
||||
|
||||
if {![file exists $dirname/punk86]} { |
||||
puts stderr "|err> build didn't seem to produce output at $dirname/punk86" |
||||
exit 2 |
||||
} |
||||
|
||||
if {![catch { |
||||
exec tasklist | grep punk86 |
||||
} still_running]} { |
||||
puts stdout "found punk86 instances still running\n" |
||||
set count_killed 0 |
||||
foreach ln [split $still_running \n] { |
||||
puts stdout " $ln" |
||||
set pid [lindex $ln 1] |
||||
if {$forcekill} { |
||||
set killcmd [list taskkill /F /PID $pid] |
||||
} else { |
||||
set killcmd [list taskkill /PID $pid] |
||||
} |
||||
|
||||
puts stdout " pid: $pid (attempting to kill now using '$killcmd')" |
||||
|
||||
if {[catch { |
||||
exec {*}$killcmd |
||||
} errMsg]} { |
||||
puts stderr "taskkill /PID $pid returned an error:" |
||||
puts stderr $errMsg |
||||
puts stderr "(try '[info script] -k' option to force kill)" |
||||
exit 4 |
||||
} else { |
||||
puts stderr "taskkill /PID $pid ran without error" |
||||
incr count_killed |
||||
} |
||||
} |
||||
if {$count_killed > 0} { |
||||
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable" |
||||
after 2000 |
||||
} |
||||
} else { |
||||
puts stderr "Ok.. no running punk processes found" |
||||
} |
||||
|
||||
|
||||
|
||||
if {[file exists $dirname/punk86.exe]} { |
||||
puts stderr "deleting existing $dirname/punk86.exe" |
||||
if {[catch { |
||||
file delete $dirname/punk86.exe |
||||
} msg]} { |
||||
puts stderr "Failed to delete $dirname/punk86.exe" |
||||
exit 3 |
||||
} |
||||
} |
||||
|
||||
#is this test necessary? |
||||
if {[file exists $dirname/punk86.exe]} { |
||||
puts stderr "deletion of $dirname/punk86.exe failed - locked?" |
||||
exit 3 |
||||
} |
||||
|
||||
file rename $dirname/punk86 $dirname/punk86.exe |
||||
after 500 |
||||
set deployment_folder [file dirname $dirname] |
||||
|
||||
if {[file exists $deployment_folder/punk86.exe]} { |
||||
puts stderr "deleting existing deployed at $deployment_folder/punk86.exe" |
||||
if {[catch { |
||||
file delete $deployment_folder/punk86.exe |
||||
} errMsg]} { |
||||
puts stderr "deletion of deployed version at $deployment_folder/punk86.exe failed: $errMsg" |
||||
exit 4 |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
puts stdout "copying.." |
||||
puts stdout "$dirname/punk86.exe" |
||||
puts stdout "to:" |
||||
puts stdout "$deployment_folder/punk86.exe" |
||||
after 500 |
||||
file copy $dirname/punk86.exe $deployment_folder/punk86.exe |
||||
|
||||
puts stdout "done" |
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,19 @@
|
||||
2010-03-05 |
||||
- allow #tarjar-loadscript.tcl (no package name or version number) |
||||
|
||||
2008-03-15 (tarpack) |
||||
- version 1.1.3 |
||||
- modified for use in environments where ::env(HOME) is not available (certain operations such as 'file normalize ~' etc depend on it) |
||||
|
||||
2006-08-08 (tarpack) |
||||
- bump version to 1.1.2 |
||||
- fix tarpack::wrap so that the 'scriptTidy' string doesn't force the wrapped package to be dependent on tarpack. |
||||
i.e call to 'tarpack::disconnect' now wrapped in test for 'tarpack'. |
||||
This allows a tcl-only script to be wrapped with tarpack, but not require tarpack on deployment. |
||||
|
||||
2006-08-15 |
||||
- tarjar 1.0 created as a fork of tarpack 1.1.2 (which is to be abandoned as it's too close in name to 'starpack'). |
||||
|
||||
2008-06-06 |
||||
- tarjar 1.1 |
||||
|
@ -0,0 +1,3 @@
|
||||
Identifier: tarjar |
||||
Version: 2.3 |
||||
Rights: BSD |
@ -0,0 +1,11 @@
|
||||
A tarjar is a standard tar (ustar or gnu format) archive which is also directly loadable as a Tcl module without requiring the tarball to be unarchived. |
||||
|
||||
When the tarjar is placed on the Tcl module-path it can be loaded with: |
||||
package require <PackageName> |
||||
Where the <PackageName> is the segment of the tarjar filename before the dash and version number. |
||||
|
||||
!NOTE! The tarjar can be unpacked in the same folder as the .tm file, in which case, the unpacked files will take precedence over the wrapped version. |
||||
The wrapped (.tm) version however needs to remain in place to 'redirect' to the unwrapped version. |
||||
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
apply {code { |
||||
set scriptpath [file normalize [info script]] |
||||
if {[string match "#tarjar-loadscript*.tcl" [file tail $scriptpath]]} { |
||||
#jump up an extra dir level if we are within a #tarjar-loadscript file. |
||||
set mypath [file dirname [file dirname $scriptpath]] |
||||
set modver [string range [file tail [file dirname $scriptpath]] 8 end] ;# the containing folder is named #tarjar-<module>-<ver> |
||||
} else { |
||||
set mypath [file dirname $scriptpath] |
||||
set modver [file root [file tail [info script]]] |
||||
} |
||||
set mysegs [file split $mypath] |
||||
set overhang [list] |
||||
foreach libpath [tcl::tm::list] { |
||||
set libsegs [file split $libpath] ;#split and rejoin with '/' because sometimes module paths may have mixed \ & / |
||||
if {[file join $mysegs /] eq [file join [lrange $libsegs 0 [llength $mysegs]] /]} { |
||||
#mypath is below libpath |
||||
set overhang [lrange $mysegs [llength $libsegs]+1 end] |
||||
break |
||||
} |
||||
} |
||||
lassign [split $modver -] moduletail version |
||||
set ns [join [concat $overhang $moduletail] ::] |
||||
if {![catch {package require tarjar}]} { |
||||
::tarjar::disconnect [info script] |
||||
} |
||||
package provide $ns $version |
||||
namespace eval $ns $code |
||||
} ::} { |
||||
# |
||||
# Module procs here, where current namespace is that of the module. |
||||
# Package version can, if needed, be accessed as [uplevel 1 {set version}] |
||||
# Last element of module name: [uplevel 1 {set moduletail}] |
||||
# Full module name: [uplevel 1 {set ns}] |
||||
|
||||
#<modulecode> |
||||
# |
||||
#</modulecode> |
||||
|
||||
#<sourcefiles> |
||||
# |
||||
#</sourcefiles> |
||||
|
||||
#<loadfiles> |
||||
# |
||||
#</loadfiles> |
||||
|
||||
} |
@ -0,0 +1,78 @@
|
||||
apply {code { |
||||
set scriptpath [file normalize [info script]] |
||||
if {[string match "#tarjar-loadscript*.tcl" [file tail $scriptpath]]} { |
||||
#jump up an extra dir level if we are within a #tarjar-loadscript file. |
||||
set mypath [file dirname [file dirname $scriptpath]] |
||||
set modver [string range [file tail [file dirname $scriptpath]] 8 end] ;# the containing folder is named #tarjar-<module>-<ver> |
||||
} else { |
||||
set mypath [file dirname $scriptpath] |
||||
set modver [file root [file tail [info script]]] |
||||
} |
||||
set mysegs [file split $mypath] |
||||
set mysegs_rejoined [file join $mysegs /] |
||||
set mysegs_count [llength $mysegs] |
||||
set overhang [list] |
||||
foreach libpath [tcl::tm::list] { |
||||
set libsegs [file split $libpath] ;#split and rejoin with '/' because sometimes module paths may have mixed \ & / |
||||
if {$mysegs_rejoined eq [file join [lrange $libsegs 0 $mysegs_count] /]} { |
||||
#mypath is below libpath |
||||
set overhang [lrange $mysegs [llength $libsegs]+1 end] |
||||
break |
||||
} |
||||
} |
||||
lassign [split $modver -] moduletail version |
||||
set ns [join [concat $overhang $moduletail] ::] |
||||
if {![catch {package require tarjar}]} { |
||||
lassign [::tarjar::glob ${moduletail}/load/[::tarjar::platform::generic] *] _ok libs ;#assume all files are loadable.. |
||||
if {[llength $libs] > 0} { |
||||
foreach lname $libs { |
||||
tarjar::load ${moduletail}/load/[::tarjar::platform::generic]/$lname -from [info script] |
||||
} |
||||
} else { |
||||
lassign [::tarjar::glob ${moduletail}/load *] _ok binfolders |
||||
set some_bin_exists false ;#keep track of whether there is at least one binary in the archive for some architecture. |
||||
set architectures_supported [list] |
||||
foreach fldr $binfolders { |
||||
lassign [::tarjar::glob ${moduletail}/load/$fldr *] _ok binfile |
||||
if {[llength $binfile] > 0} { |
||||
set some_bin_exists true |
||||
lappend architectures_supported $fldr |
||||
} |
||||
} |
||||
if {[llength $architectures_supported] > 0} { |
||||
puts stderr "platform [tarjar::platform::generic] not supported by the tarjar load-script. (archive contains binaries for:[join $architectures_supported ,])" |
||||
} |
||||
} |
||||
#load all other .tcl files in the *tarjar base* folder - ie those not explicitly wrapped in the {modulename} folder |
||||
#!todo - use a manifest to determine load order? |
||||
set basefolder [file dirname $moduletail] |
||||
lassign [::tarjar::glob $basefolder *.tcl] _ok sourcefiles |
||||
foreach f $sourcefiles { |
||||
if {![string match "#*" $f]} { |
||||
namespace eval $ns [list tarjar::source $basefolder/$f] |
||||
} |
||||
} |
||||
::tarjar::disconnect [info script] |
||||
} |
||||
package provide $ns $version |
||||
namespace eval $ns $code |
||||
} ::} { |
||||
# |
||||
# Module procs here, where current namespace is that of the module. |
||||
# Package version can, if needed, be accessed as [uplevel 1 {set version}] |
||||
# Last element of module name: [uplevel 1 {set moduletail}] |
||||
# Full module name: [uplevel 1 {set ns}] |
||||
|
||||
#<modulecode> |
||||
# |
||||
#</modulecode> |
||||
|
||||
#<sourcefiles> |
||||
# |
||||
#</sourcefiles> |
||||
|
||||
#<loadfiles> |
||||
# |
||||
#</loadfiles> |
||||
|
||||
} |
@ -0,0 +1,11 @@
|
||||
A tarjar is a standard tar (ustar or gnu format) archive which is also directly loadable as a Tcl module without requiring the tarball to be unarchived. |
||||
|
||||
When the tarjar is placed on the Tcl module-path it can be loaded with: |
||||
package require <PackageName> |
||||
Where the <PackageName> is the segment of the tarjar filename before the dash and version number. |
||||
|
||||
!NOTE! The tarjar can be unpacked in the same folder as the .tm file, in which case, the unpacked files will take precedence over the wrapped version. |
||||
The wrapped version however needs to remain in place to 'redirect' to the unwrapped version. |
||||
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
split tarjar read functionality into separate file/namespace. |
||||
Work out how to make tarjar self-hosting. ie wrap next version of tarjar using current one. |
||||
(use a 'rollup' of the read-code in the generated loadscript?) |
@ -0,0 +1,259 @@
|
||||
|
||||
# Tcl parser for optional arguments in function calls and |
||||
# commandline arguments |
||||
# |
||||
# (c) 2001 Bastien Chevreux |
||||
|
||||
# Index of exported commands |
||||
# - argp::registerArgs |
||||
# - argp::setArgDefaults |
||||
# - argp::setArgsNeeded |
||||
# - argp::parseArgs |
||||
|
||||
# Internal commands |
||||
# - argp::CheckValues |
||||
|
||||
# See end of file for an example on how to use |
||||
|
||||
package provide argp 0.2 |
||||
|
||||
namespace eval argp { |
||||
variable Optstore |
||||
variable Opttypes { |
||||
boolean integer double string |
||||
} |
||||
|
||||
namespace export {[a-z]*} |
||||
} |
||||
|
||||
|
||||
proc argp::registerArgs { func arglist } { |
||||
variable Opttypes |
||||
variable Optstore |
||||
|
||||
set parentns [string range [uplevel 1 [list namespace current]] 2 end] |
||||
if { $parentns != "" } { |
||||
append caller $parentns :: $func |
||||
} else { |
||||
set caller $func |
||||
} |
||||
set cmangled [string map {:: _} $caller] |
||||
|
||||
#puts $parentns |
||||
#puts $caller |
||||
#puts $cmangled |
||||
|
||||
set Optstore(keys,$cmangled) {} |
||||
set Optstore(deflist,$cmangled) {} |
||||
set Optstore(argneeded,$cmangled) {} |
||||
|
||||
foreach arg $arglist { |
||||
foreach {opt type default allowed} $arg { |
||||
set optindex [lsearch -glob $Opttypes $type*] |
||||
if { $optindex < 0} { |
||||
return -code error "$caller, unknown type $type while registering arguments.\nAllowed types: [string trim $Opttypes]" |
||||
} |
||||
set type [lindex $Opttypes $optindex] |
||||
|
||||
lappend Optstore(keys,$cmangled) $opt |
||||
set Optstore(type,$opt,$cmangled) $type |
||||
set Optstore(default,$opt,$cmangled) $default |
||||
set Optstore(allowed,$opt,$cmangled) $allowed |
||||
lappend Optstore(deflist,$cmangled) $opt $default |
||||
} |
||||
} |
||||
|
||||
if { [catch {CheckValues $caller $cmangled $Optstore(deflist,$cmangled)} res]} { |
||||
return -code error "Error in declaration of optional arguments.\n$res" |
||||
} |
||||
} |
||||
|
||||
proc argp::setArgDefaults { func arglist } { |
||||
variable Optstore |
||||
|
||||
set parentns [string range [uplevel 1 [list namespace current]] 2 end] |
||||
if { $parentns != "" } { |
||||
append caller $parentns :: $func |
||||
} else { |
||||
set caller $func |
||||
} |
||||
set cmangled [string map {:: _} $caller] |
||||
|
||||
if {![info exists Optstore(deflist,$cmangled)]} { |
||||
return -code error "Arguments for $caller not registered yet." |
||||
} |
||||
set Optstore(deflist,$cmangled) {} |
||||
foreach {opt default} $arglist { |
||||
if {![info exists Optstore(default,$opt,$cmangled)]} { |
||||
return -code error "$caller, unknown option $opt, must be one of: $Optstore(keys,$cmangled)" |
||||
} |
||||
set Optstore(default,$opt,$cmangled) $default |
||||
} |
||||
|
||||
# set the new defaultlist |
||||
foreach opt $Optstore(keys,$cmangled) { |
||||
lappend Optstore(deflist,$cmangled) $opt $Optstore(default,$opt,$cmangled) |
||||
} |
||||
} |
||||
|
||||
proc argp::setArgsNeeded { func arglist } { |
||||
variable Optstore |
||||
|
||||
set parentns [string range [uplevel 1 [list namespace current]] 2 end] |
||||
if { $parentns != "" } { |
||||
append caller $parentns :: $func |
||||
} else { |
||||
set caller $func |
||||
} |
||||
set cmangled [string map {:: _} $caller] |
||||
|
||||
#append caller $parentns :: $func |
||||
#set cmangled ${parentns}_$func |
||||
|
||||
if {![info exists Optstore(deflist,$cmangled)]} { |
||||
return -code error "Arguments for $caller not registered yet." |
||||
} |
||||
|
||||
set Optstore(argneeded,$cmangled) {} |
||||
foreach opt $arglist { |
||||
if {![info exists Optstore(default,$opt,$cmangled)]} { |
||||
return -code error "$caller, unknown option $opt, must be one of: $Optstore(keys,$cmangled)" |
||||
} |
||||
lappend Optstore(argneeded,$cmangled) $opt |
||||
} |
||||
} |
||||
|
||||
|
||||
proc argp::parseArgs { args } { |
||||
variable Optstore |
||||
|
||||
if {[llength $args] == 0} { |
||||
upvar args a opts o |
||||
} else { |
||||
upvar args a [lindex $args 0] o |
||||
} |
||||
|
||||
if { [ catch { set caller [lindex [info level -1] 0]}]} { |
||||
set caller "main program" |
||||
set cmangled "" |
||||
} else { |
||||
set cmangled [string map {:: _} $caller] |
||||
} |
||||
|
||||
if {![info exists Optstore(deflist,$cmangled)]} { |
||||
return -code error "Arguments for $caller not registered yet." |
||||
} |
||||
|
||||
# set the defaults |
||||
array set o $Optstore(deflist,$cmangled) |
||||
|
||||
# but unset the needed arguments |
||||
foreach key $Optstore(argneeded,$cmangled) { |
||||
catch { unset o($key) } |
||||
} |
||||
|
||||
foreach {key val} $a { |
||||
if {![info exists Optstore(type,$key,$cmangled)]} { |
||||
return -code error "$caller, unknown option $key, must be one of: $Optstore(keys,$cmangled)" |
||||
} |
||||
switch -exact -- $Optstore(type,$key,$cmangled) { |
||||
boolean - |
||||
integer { |
||||
if { $val == "" } { |
||||
return -code error "$caller, $key empty string is not $Optstore(type,$key,$cmangled) value." |
||||
} |
||||
if { ![string is $Optstore(type,$key,$cmangled) $val]} { |
||||
return -code error "$caller, $key $val is not $Optstore(type,$key,$cmangled) value." |
||||
} |
||||
} |
||||
double { |
||||
if { $val == "" } { |
||||
return -code error "$caller, $key empty string is not double value." |
||||
} |
||||
if { ![string is double $val]} { |
||||
return -code error "$caller, $key $val is not double value." |
||||
} |
||||
if { [string is integer $val]} { |
||||
set val [expr {$val + .0}] |
||||
} |
||||
} |
||||
default { |
||||
} |
||||
} |
||||
set o($key) $val |
||||
} |
||||
|
||||
foreach key $Optstore(argneeded,$cmangled) { |
||||
if {![info exists o($key)]} { |
||||
return -code error "$caller, needed argument $key was not given." |
||||
} |
||||
} |
||||
|
||||
if { [catch { CheckValues $caller $cmangled [array get o]} err]} { |
||||
return -code error $err |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
|
||||
proc argp::CheckValues { caller cmangled checklist } { |
||||
variable Optstore |
||||
|
||||
#puts "Checking $checklist" |
||||
|
||||
foreach {key val} $checklist { |
||||
if { [llength $Optstore(allowed,$key,$cmangled)] > 0 } { |
||||
switch -exact -- $Optstore(type,$key,$cmangled) { |
||||
string { |
||||
if { [lsearch $Optstore(allowed,$key,$cmangled) $val] < 0} { |
||||
return -code error "$caller, $key $val is not in allowed values: $Optstore(allowed,$key,$cmangled)" |
||||
} |
||||
} |
||||
double - |
||||
integer { |
||||
set found 0 |
||||
foreach range $Optstore(allowed,$key,$cmangled) { |
||||
if {[llength $range] == 1} { |
||||
if { $val == [lindex $range 0] } { |
||||
set found 1 |
||||
break |
||||
} |
||||
} elseif {[llength $range] == 2} { |
||||
set low [lindex $range 0] |
||||
set high [lindex $range 1] |
||||
|
||||
if { ![string is integer $low] \ |
||||
&& [string compare "-" $low] != 0} { |
||||
return -code error "$caller, $key of type $Optstore(type,$key,$cmangled) has a lower value range that is not integer and not ´-´: $range" |
||||
} |
||||
if { ![string is integer $high] \ |
||||
&& [string compare "+" $high] != 0} { |
||||
return -code error "$caller, $key of type $Optstore(type,$key,$cmangled) has a upper value range that is not integer and not ´+´: $range" |
||||
} |
||||
if {[string compare "-" $low] == 0} { |
||||
if { [string compare "+" $high] == 0 \ |
||||
|| $val <= $high } { |
||||
set found 1 |
||||
break |
||||
} |
||||
} |
||||
if { $val >= $low } { |
||||
if {[string compare "+" $high] == 0 \ |
||||
|| $val <= $high } { |
||||
set found 1 |
||||
break |
||||
} |
||||
} |
||||
} else { |
||||
return -code error "$caller, $key of type $Optstore(type,$key,$cmangled) has an allowed value range containing more than 2 elements: $range" |
||||
} |
||||
} |
||||
if { $found == 0 } { |
||||
return -code error "$caller, $key $val is not covered by allowed ranges: $Optstore(allowed,$key,$cmangled)" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,157 @@
|
||||
|
||||
package provide [lassign {overtype 1.3} pkg ver]$pkg [namespace eval $pkg[set pkg {}] {list [variable version $ver[set ver {}]]$version}] |
||||
|
||||
#Julian Noble <julian@precisium.com.au> - 2003 |
||||
#Released under standard 'BSD license' conditions. |
||||
# |
||||
#todo - ellipsis truncation indicator for center,right |
||||
|
||||
|
||||
namespace eval overtype { |
||||
namespace export * |
||||
} |
||||
proc overtype::about {} { |
||||
return "Simple text formatting. Author JMN. BSD-License" |
||||
} |
||||
|
||||
|
||||
proc overtype::left {args} { |
||||
# @c overtype starting at left (overstrike) |
||||
# @c can/should we use something like this?: 'format "%-*s" $len $overtext |
||||
|
||||
if {[llength $args] < 2} { |
||||
error {usage: ?-overflow [1|0]? ?-ellipsis [1|0]? ?-ellipsistext ...? undertext overtext} |
||||
} |
||||
foreach {undertext overtext} [lrange $args end-1 end] break |
||||
|
||||
set opt(-ellipsis) 0 |
||||
set opt(-ellipsistext) {...} |
||||
set opt(-overflow) 0 |
||||
array set opt [lrange $args 0 end-2] |
||||
|
||||
|
||||
set len [string length $undertext] |
||||
set overlen [string length $overtext] |
||||
set diff [expr {$overlen - $len}] |
||||
if {$diff > 0} { |
||||
if {$opt(-overflow)} { |
||||
return $overtext |
||||
} else { |
||||
if {$opt(-ellipsis)} { |
||||
return [overtype::right [string range $overtext 0 [expr {$len -1}]] $opt(-ellipsistext)] |
||||
} else { |
||||
return [string range $overtext 0 [expr {$len -1}]] |
||||
} |
||||
} |
||||
} else { |
||||
|
||||
return "$overtext[string range $undertext $overlen end]" |
||||
} |
||||
} |
||||
|
||||
# test - use more tcl8.5 features. |
||||
proc overtype::left2 {args} { |
||||
# @c overtype starting at left (overstrike) |
||||
# @c can/should we use something like this?: 'format "%-*s" $len $overtext |
||||
|
||||
if {[llength $args] < 2} { |
||||
error {usage: ?-overflow [1|0]? ?-ellipsis [1|0]? ?-ellipsistext ...? undertext overtext} |
||||
} |
||||
foreach {undertext overtext} [lrange $args end-1 end] break |
||||
|
||||
set opt(-ellipsis) 0 |
||||
set opt(-ellipsistext) {...} |
||||
set opt(-overflow) 0 |
||||
array set opt [lrange $args 0 end-2] |
||||
|
||||
|
||||
set len [string length $undertext] |
||||
set overlen [string length $overtext] |
||||
set diff [expr {$overlen - $len}] |
||||
if {$diff > 0} { |
||||
if {$opt(-overflow)} { |
||||
return $overtext |
||||
} else { |
||||
if {$opt(-ellipsis)} { |
||||
return [overtype::right [string range $overtext 0 $len-1] $opt(-ellipsistext)] |
||||
} else { |
||||
return [string range $overtext 0 $len-1 ] |
||||
} |
||||
} |
||||
} else { |
||||
#return "$overtext[string range $undertext $overlen end]" |
||||
return [string replace $undertext 0 $overlen-1 $overtext] |
||||
} |
||||
} |
||||
proc overtype::centre {args} { |
||||
if {[llength $args] < 2} { |
||||
error {usage: ?-bias [left|right]? ?-overflow [1|0]? undertext overtext} |
||||
} |
||||
foreach {undertext overtext} [lrange $args end-1 end] break |
||||
|
||||
set opt(-bias) left |
||||
set opt(-overflow) 0 |
||||
array set opt [lrange $args 0 end-2] |
||||
|
||||
|
||||
set olen [string length $overtext] |
||||
set ulen [string length $undertext] |
||||
set diff [expr {$ulen - $olen}] |
||||
if {$diff > 0} { |
||||
set half [expr {round(int($diff / 2))}] |
||||
if {[string match right $opt(-bias)]} { |
||||
if {[expr {2 * $half}] < $diff} { |
||||
incr half |
||||
} |
||||
} |
||||
|
||||
set rhs [expr {$diff - $half - 1}] |
||||
set lhs [expr {$half - 1}] |
||||
|
||||
set a [string range $undertext 0 $lhs] |
||||
set b $overtext |
||||
set c [string range $undertext end-$rhs end] |
||||
return $a$b$c |
||||
} else { |
||||
if {$diff < 0} { |
||||
if {$opt(-overflow)} { |
||||
return $overtext |
||||
} else { |
||||
return [string range $overtext 0 [expr {$ulen - 1}]] |
||||
} |
||||
} else { |
||||
return $overtext |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc overtype::right {args} { |
||||
# @d !todo - implement overflow, length checks etc |
||||
|
||||
if {[llength $args] < 2} { |
||||
error {usage: ?-overflow [1|0]? undertext overtext} |
||||
} |
||||
foreach {undertext overtext} [lrange $args end-1 end] break |
||||
|
||||
set opt(-overflow) 0 |
||||
array set opt [lrange $args 0 end-2] |
||||
|
||||
|
||||
set olen [string length $overtext] |
||||
set ulen [string length $undertext] |
||||
|
||||
if {$opt(-overflow)} { |
||||
return [string range $undertext 0 end-$olen]$overtext |
||||
} else { |
||||
if {$olen > $ulen} { |
||||
set diff [expr {$olen - $ulen}] |
||||
return [string range $undertext 0 end-$olen][string range $overtext 0 end-$diff] |
||||
} else { |
||||
return [string range $undertext 0 end-$olen]$overtext |
||||
} |
||||
} |
||||
} |
||||
|
||||
namespace eval overtype { |
||||
interp alias {} ::overtype::center {} ::overtype::centre |
||||
} |
@ -0,0 +1,428 @@
|
||||
# -*- tcl -*- |
||||
# ### ### ### ######### ######### ######### |
||||
## Overview |
||||
|
||||
# Heuristics to assemble a platform identifier from publicly available |
||||
# information. The identifier describes the platform of the currently |
||||
# running tcl shell. This is a mixture of the runtime environment and |
||||
# of build-time properties of the executable itself. |
||||
# |
||||
# Examples: |
||||
# <1> A tcl shell executing on a x86_64 processor, but having a |
||||
# wordsize of 4 was compiled for the x86 environment, i.e. 32 |
||||
# bit, and loaded packages have to match that, and not the |
||||
# actual cpu. |
||||
# |
||||
# <2> The hp/solaris 32/64 bit builds of the core cannot be |
||||
# distinguished by looking at tcl_platform. As packages have to |
||||
# match the 32/64 information we have to look in more places. In |
||||
# this case we inspect the executable itself (magic numbers, |
||||
# i.e. fileutil::magic::filetype). |
||||
# |
||||
# The basic information used comes out of the 'os' and 'machine' |
||||
# entries of the 'tcl_platform' array. A number of general and |
||||
# os/machine specific transformation are applied to get a canonical |
||||
# result. |
||||
# |
||||
# General |
||||
# Only the first element of 'os' is used - we don't care whether we |
||||
# are on "Windows NT" or "Windows XP" or whatever. |
||||
# |
||||
# Machine specific |
||||
# % amd64 -> x86_64 |
||||
# % arm* -> arm |
||||
# % sun4* -> sparc |
||||
# % ia32* -> ix86 |
||||
# % intel -> ix86 |
||||
# % i*86* -> ix86 |
||||
# % Power* -> powerpc |
||||
# % x86_64 + wordSize 4 => x86 code |
||||
# |
||||
# OS specific |
||||
# % AIX are always powerpc machines |
||||
# % HP-UX 9000/800 etc means parisc |
||||
# % linux has to take glibc version into account |
||||
# % sunos -> solaris, and keep version number |
||||
# |
||||
# NOTE: A platform like linux glibc 2.3, which can use glibc 2.2 stuff |
||||
# has to provide all possible allowed platform identifiers when |
||||
# searching search. Ditto a solaris 2.8 platform can use solaris 2.6 |
||||
# packages. Etc. This is handled by the other procedure, see below. |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Requirements |
||||
|
||||
namespace eval ::platform {} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Implementation |
||||
|
||||
# -- platform::generic |
||||
# |
||||
# Assembles an identifier for the generic platform. It leaves out |
||||
# details like kernel version, libc version, etc. |
||||
|
||||
proc ::platform::generic {} { |
||||
global tcl_platform |
||||
|
||||
set plat [string tolower [lindex $tcl_platform(os) 0]] |
||||
set cpu $tcl_platform(machine) |
||||
|
||||
switch -glob -- $cpu { |
||||
sun4* { |
||||
set cpu sparc |
||||
} |
||||
intel - |
||||
ia32* - |
||||
i*86* { |
||||
set cpu ix86 |
||||
} |
||||
x86_64 { |
||||
if {$tcl_platform(wordSize) == 4} { |
||||
# See Example <1> at the top of this file. |
||||
set cpu ix86 |
||||
} |
||||
} |
||||
ppc - |
||||
"Power*" { |
||||
set cpu powerpc |
||||
} |
||||
"arm*" { |
||||
set cpu arm |
||||
} |
||||
ia64 { |
||||
if {$tcl_platform(wordSize) == 4} { |
||||
append cpu _32 |
||||
} |
||||
} |
||||
} |
||||
|
||||
switch -glob -- $plat { |
||||
windows { |
||||
if {$tcl_platform(platform) == "unix"} { |
||||
set plat cygwin |
||||
} else { |
||||
set plat win32 |
||||
} |
||||
if {$cpu eq "amd64"} { |
||||
# Do not check wordSize, win32-x64 is an IL32P64 platform. |
||||
set cpu x86_64 |
||||
} |
||||
} |
||||
sunos { |
||||
set plat solaris |
||||
if {[string match "ix86" $cpu]} { |
||||
if {$tcl_platform(wordSize) == 8} { |
||||
set cpu x86_64 |
||||
} |
||||
} elseif {![string match "ia64*" $cpu]} { |
||||
# sparc |
||||
if {$tcl_platform(wordSize) == 8} { |
||||
append cpu 64 |
||||
} |
||||
} |
||||
} |
||||
darwin { |
||||
set plat macosx |
||||
# Correctly identify the cpu when running as a 64bit |
||||
# process on a machine with a 32bit kernel |
||||
if {$cpu eq "ix86"} { |
||||
if {$tcl_platform(wordSize) == 8} { |
||||
set cpu x86_64 |
||||
} |
||||
} |
||||
} |
||||
aix { |
||||
set cpu powerpc |
||||
if {$tcl_platform(wordSize) == 8} { |
||||
append cpu 64 |
||||
} |
||||
} |
||||
hp-ux { |
||||
set plat hpux |
||||
if {![string match "ia64*" $cpu]} { |
||||
set cpu parisc |
||||
if {$tcl_platform(wordSize) == 8} { |
||||
append cpu 64 |
||||
} |
||||
} |
||||
} |
||||
osf1 { |
||||
set plat tru64 |
||||
} |
||||
default { |
||||
set plat [lindex [split $plat _-] 0] |
||||
} |
||||
} |
||||
|
||||
return "${plat}-${cpu}" |
||||
} |
||||
|
||||
# -- platform::identify |
||||
# |
||||
# Assembles an identifier for the exact platform, by extending the |
||||
# generic identifier. I.e. it adds in details like kernel version, |
||||
# libc version, etc., if they are relevant for the loading of |
||||
# packages on the platform. |
||||
|
||||
proc ::platform::identify {} { |
||||
global tcl_platform |
||||
|
||||
set id [generic] |
||||
regexp {^([^-]+)-([^-]+)$} $id -> plat cpu |
||||
|
||||
switch -- $plat { |
||||
solaris { |
||||
regsub {^5} $tcl_platform(osVersion) 2 text |
||||
append plat $text |
||||
return "${plat}-${cpu}" |
||||
} |
||||
macosx { |
||||
set major [lindex [split $tcl_platform(osVersion) .] 0] |
||||
if {$major > 19} { |
||||
set minor [lindex [split $tcl_platform(osVersion) .] 1] |
||||
incr major -9 |
||||
append plat $major.[expr {$minor - 1}] |
||||
} else { |
||||
incr major -4 |
||||
append plat 10.$major |
||||
return "${plat}-${cpu}" |
||||
} |
||||
return "${plat}-${cpu}" |
||||
} |
||||
linux { |
||||
# Look for the libc*.so and determine its version |
||||
# (libc5/6, libc6 further glibc 2.X) |
||||
|
||||
set v unknown |
||||
|
||||
# Determine in which directory to look. /lib, or /lib64. |
||||
# For that we use the tcl_platform(wordSize). |
||||
# |
||||
# We could use the 'cpu' info, per the equivalence below, |
||||
# that however would be restricted to intel. And this may |
||||
# be a arm, mips, etc. system. The wordsize is more |
||||
# fundamental. |
||||
# |
||||
# ix86 <=> (wordSize == 4) <=> 32 bit ==> /lib |
||||
# x86_64 <=> (wordSize == 8) <=> 64 bit ==> /lib64 |
||||
# |
||||
# Do not look into /lib64 even if present, if the cpu |
||||
# doesn't fit. |
||||
|
||||
# TODO: Determine the prefixes (i386, x86_64, ...) for |
||||
# other cpus. The path after the generic one is utterly |
||||
# specific to intel right now. Ok, on Ubuntu, possibly |
||||
# other Debian systems we may apparently be able to query |
||||
# the necessary CPU code. If we can't we simply use the |
||||
# hardwired fallback. |
||||
|
||||
switch -exact -- $tcl_platform(wordSize) { |
||||
4 { |
||||
lappend bases /lib |
||||
if {[catch { |
||||
exec dpkg-architecture -qDEB_HOST_MULTIARCH |
||||
} res]} { |
||||
lappend bases /lib/i386-linux-gnu |
||||
} else { |
||||
# dpkg-arch returns the full tripled, not just cpu. |
||||
lappend bases /lib/$res |
||||
} |
||||
} |
||||
8 { |
||||
lappend bases /lib64 |
||||
if {[catch { |
||||
exec dpkg-architecture -qDEB_HOST_MULTIARCH |
||||
} res]} { |
||||
lappend bases /lib/x86_64-linux-gnu |
||||
} else { |
||||
# dpkg-arch returns the full tripled, not just cpu. |
||||
lappend bases /lib/$res |
||||
} |
||||
} |
||||
default { |
||||
return -code error "Bad wordSize $tcl_platform(wordSize), expected 4 or 8" |
||||
} |
||||
} |
||||
|
||||
foreach base $bases { |
||||
if {[LibcVersion $base -> v]} break |
||||
} |
||||
|
||||
append plat -$v |
||||
return "${plat}-${cpu}" |
||||
} |
||||
} |
||||
|
||||
return $id |
||||
} |
||||
|
||||
proc ::platform::LibcVersion {base _->_ vv} { |
||||
upvar 1 $vv v |
||||
set libclist [lsort [glob -nocomplain -directory $base libc*]] |
||||
|
||||
if {![llength $libclist]} { return 0 } |
||||
|
||||
set libc [lindex $libclist 0] |
||||
|
||||
# Try executing the library first. This should suceed |
||||
# for a glibc library, and return the version |
||||
# information. |
||||
|
||||
if {![catch { |
||||
set vdata [lindex [split [exec $libc] \n] 0] |
||||
}]} { |
||||
regexp {version ([0-9]+(\.[0-9]+)*)} $vdata -> v |
||||
foreach {major minor} [split $v .] break |
||||
set v glibc${major}.${minor} |
||||
return 1 |
||||
} else { |
||||
# We had trouble executing the library. We are now |
||||
# inspecting its name to determine the version |
||||
# number. This code by Larry McVoy. |
||||
|
||||
if {[regexp -- {libc-([0-9]+)\.([0-9]+)} $libc -> major minor]} { |
||||
set v glibc${major}.${minor} |
||||
return 1 |
||||
} |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
# -- platform::patterns |
||||
# |
||||
# Given an exact platform identifier, i.e. _not_ the generic |
||||
# identifier it assembles a list of exact platform identifier |
||||
# describing platform which should be compatible with the |
||||
# input. |
||||
# |
||||
# I.e. packages for all platforms in the result list should be |
||||
# loadable on the specified platform. |
||||
|
||||
# << Should we add the generic identifier to the list as well ? In |
||||
# general it is not compatible I believe. So better not. In many |
||||
# cases the exact identifier is identical to the generic one |
||||
# anyway. |
||||
# >> |
||||
|
||||
proc ::platform::patterns {id} { |
||||
set res [list $id] |
||||
if {$id eq "tcl"} {return $res} |
||||
|
||||
switch -glob -- $id { |
||||
solaris*-* { |
||||
if {[regexp {solaris([^-]*)-(.*)} $id -> v cpu]} { |
||||
if {$v eq ""} {return $id} |
||||
foreach {major minor} [split $v .] break |
||||
incr minor -1 |
||||
for {set j $minor} {$j >= 6} {incr j -1} { |
||||
lappend res solaris${major}.${j}-${cpu} |
||||
} |
||||
} |
||||
} |
||||
linux*-* { |
||||
if {[regexp {linux-glibc([^-]*)-(.*)} $id -> v cpu]} { |
||||
foreach {major minor} [split $v .] break |
||||
incr minor -1 |
||||
for {set j $minor} {$j >= 0} {incr j -1} { |
||||
lappend res linux-glibc${major}.${j}-${cpu} |
||||
} |
||||
} |
||||
} |
||||
macosx-powerpc { |
||||
lappend res macosx-universal |
||||
} |
||||
macosx-x86_64 { |
||||
lappend res macosx-i386-x86_64 |
||||
} |
||||
macosx-ix86 { |
||||
lappend res macosx-universal macosx-i386-x86_64 |
||||
} |
||||
macosx*-* { |
||||
# 10.5+,11.0+ |
||||
if {[regexp {macosx([^-]*)-(.*)} $id -> v cpu]} { |
||||
|
||||
switch -exact -- $cpu { |
||||
ix86 { |
||||
lappend alt i386-x86_64 |
||||
lappend alt universal |
||||
} |
||||
x86_64 { |
||||
if {[lindex [split $::tcl_platform(osVersion) .] 0] < 19} { |
||||
set alt i386-x86_64 |
||||
} else { |
||||
set alt {} |
||||
} |
||||
} |
||||
arm { |
||||
lappend alt x86_64 |
||||
} |
||||
default { set alt {} } |
||||
} |
||||
|
||||
if {$v ne ""} { |
||||
foreach {major minor} [split $v .] break |
||||
|
||||
set res {} |
||||
if {$major eq 11} { |
||||
# Add 11.0 to 11.minor to patterns. |
||||
for {set j $minor} {$j >= 0} {incr j -1} { |
||||
lappend res macosx${major}.${j}-${cpu} |
||||
foreach a $alt { |
||||
lappend res macosx${major}.${j}-$a |
||||
} |
||||
} |
||||
set major 10 |
||||
set minor 15 |
||||
} |
||||
# Add 10.5 to 10.minor to patterns. |
||||
for {set j $minor} {$j >= 5} {incr j -1} { |
||||
if {$cpu ne "arm"} { |
||||
lappend res macosx${major}.${j}-${cpu} |
||||
} |
||||
foreach a $alt { |
||||
lappend res macosx${major}.${j}-$a |
||||
} |
||||
} |
||||
|
||||
# Add unversioned patterns for 10.3/10.4 builds. |
||||
lappend res macosx-${cpu} |
||||
foreach a $alt { |
||||
lappend res macosx-$a |
||||
} |
||||
} else { |
||||
# No version, just do unversioned patterns. |
||||
foreach a $alt { |
||||
lappend res macosx-$a |
||||
} |
||||
} |
||||
} else { |
||||
# no v, no cpu ... nothing |
||||
} |
||||
} |
||||
} |
||||
lappend res tcl ; # Pure tcl packages are always compatible. |
||||
return $res |
||||
} |
||||
|
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Ready |
||||
|
||||
package provide platform 1.0.17 |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Demo application |
||||
|
||||
if {[info exists argv0] && ($argv0 eq [info script])} { |
||||
puts ==================================== |
||||
parray tcl_platform |
||||
puts ==================================== |
||||
puts Generic\ identification:\ [::platform::generic] |
||||
puts Exact\ identification:\ \ \ [::platform::identify] |
||||
puts ==================================== |
||||
puts Search\ patterns: |
||||
puts *\ [join [::platform::patterns [::platform::identify]] \n*\ ] |
||||
puts ==================================== |
||||
exit 0 |
||||
} |
@ -0,0 +1,241 @@
|
||||
|
||||
# -*- tcl -*- |
||||
# ### ### ### ######### ######### ######### |
||||
## Overview |
||||
|
||||
# Higher-level commands which invoke the functionality of this package |
||||
# for an arbitrary tcl shell (tclsh, wish, ...). This is required by a |
||||
# repository as while the tcl shell executing packages uses the same |
||||
# platform in general as a repository application there can be |
||||
# differences in detail (i.e. 32/64 bit builds). |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Requirements |
||||
|
||||
package require platform |
||||
namespace eval ::platform::shell {} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Implementation |
||||
|
||||
# -- platform::shell::generic |
||||
|
||||
proc ::platform::shell::generic {shell} { |
||||
# Argument is the path to a tcl shell. |
||||
|
||||
CHECK $shell |
||||
LOCATE base out |
||||
|
||||
set code {} |
||||
# Forget any pre-existing platform package, it might be in |
||||
# conflict with this one. |
||||
lappend code {package forget platform} |
||||
# Inject our platform package |
||||
lappend code [list source $base] |
||||
# Query and print the architecture |
||||
lappend code {puts [platform::generic]} |
||||
# And done |
||||
lappend code {exit 0} |
||||
|
||||
set arch [RUN $shell [join $code \n]] |
||||
|
||||
if {$out} {file delete -force $base} |
||||
return $arch |
||||
} |
||||
|
||||
# -- platform::shell::identify |
||||
|
||||
proc ::platform::shell::identify {shell} { |
||||
# Argument is the path to a tcl shell. |
||||
|
||||
CHECK $shell |
||||
LOCATE base out |
||||
|
||||
set code {} |
||||
# Forget any pre-existing platform package, it might be in |
||||
# conflict with this one. |
||||
lappend code {package forget platform} |
||||
# Inject our platform package |
||||
lappend code [list source $base] |
||||
# Query and print the architecture |
||||
lappend code {puts [platform::identify]} |
||||
# And done |
||||
lappend code {exit 0} |
||||
|
||||
set arch [RUN $shell [join $code \n]] |
||||
|
||||
if {$out} {file delete -force $base} |
||||
return $arch |
||||
} |
||||
|
||||
# -- platform::shell::platform |
||||
|
||||
proc ::platform::shell::platform {shell} { |
||||
# Argument is the path to a tcl shell. |
||||
|
||||
CHECK $shell |
||||
|
||||
set code {} |
||||
lappend code {puts $tcl_platform(platform)} |
||||
lappend code {exit 0} |
||||
|
||||
return [RUN $shell [join $code \n]] |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Internal helper commands. |
||||
|
||||
proc ::platform::shell::CHECK {shell} { |
||||
if {![file exists $shell]} { |
||||
return -code error "Shell \"$shell\" does not exist" |
||||
} |
||||
if {![file executable $shell]} { |
||||
return -code error "Shell \"$shell\" is not executable (permissions)" |
||||
} |
||||
return |
||||
} |
||||
|
||||
proc ::platform::shell::LOCATE {bv ov} { |
||||
upvar 1 $bv base $ov out |
||||
|
||||
# Locate the platform package for injection into the specified |
||||
# shell. We are using package management to find it, whereever it |
||||
# is, instead of using hardwired relative paths. This allows us to |
||||
# install the two packages as TMs without breaking the code |
||||
# here. If the found package is wrapped we copy the code somewhere |
||||
# where the spawned shell will be able to read it. |
||||
|
||||
# This code is brittle, it needs has to adapt to whatever changes |
||||
# are made to the TM code, i.e. the provide statement generated by |
||||
# tm.tcl |
||||
|
||||
set pl [package ifneeded platform [package require platform]] |
||||
set base [lindex $pl end] |
||||
|
||||
set out 0 |
||||
if {[lindex [file system $base]] ne "native"} { |
||||
set temp [TEMP] |
||||
file copy -force $base $temp |
||||
set base $temp |
||||
set out 1 |
||||
} |
||||
return |
||||
} |
||||
|
||||
proc ::platform::shell::RUN {shell code} { |
||||
set c [TEMP] |
||||
set cc [open $c w] |
||||
puts $cc $code |
||||
close $cc |
||||
|
||||
set e [TEMP] |
||||
|
||||
set code [catch { |
||||
exec $shell $c 2> $e |
||||
} res] |
||||
|
||||
file delete $c |
||||
|
||||
if {$code} { |
||||
append res \n[read [set chan [open $e r]]][close $chan] |
||||
file delete $e |
||||
return -code error "Shell \"$shell\" is not executable ($res)" |
||||
} |
||||
|
||||
file delete $e |
||||
return $res |
||||
} |
||||
|
||||
proc ::platform::shell::TEMP {} { |
||||
set prefix platform |
||||
|
||||
# This code is copied out of Tcllib's fileutil package. |
||||
# (TempFile/tempfile) |
||||
|
||||
set tmpdir [DIR] |
||||
|
||||
set chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" |
||||
set nrand_chars 10 |
||||
set maxtries 10 |
||||
set access [list RDWR CREAT EXCL TRUNC] |
||||
set permission 0600 |
||||
set channel "" |
||||
set checked_dir_writable 0 |
||||
set mypid [pid] |
||||
for {set i 0} {$i < $maxtries} {incr i} { |
||||
set newname $prefix |
||||
for {set j 0} {$j < $nrand_chars} {incr j} { |
||||
append newname [string index $chars \ |
||||
[expr {int(rand()*62)}]] |
||||
} |
||||
set newname [file join $tmpdir $newname] |
||||
if {[file exists $newname]} { |
||||
after 1 |
||||
} else { |
||||
if {[catch {open $newname $access $permission} channel]} { |
||||
if {!$checked_dir_writable} { |
||||
set dirname [file dirname $newname] |
||||
if {![file writable $dirname]} { |
||||
return -code error "Directory $dirname is not writable" |
||||
} |
||||
set checked_dir_writable 1 |
||||
} |
||||
} else { |
||||
# Success |
||||
close $channel |
||||
return [file normalize $newname] |
||||
} |
||||
} |
||||
} |
||||
if {$channel ne ""} { |
||||
return -code error "Failed to open a temporary file: $channel" |
||||
} else { |
||||
return -code error "Failed to find an unused temporary file name" |
||||
} |
||||
} |
||||
|
||||
proc ::platform::shell::DIR {} { |
||||
# This code is copied out of Tcllib's fileutil package. |
||||
# (TempDir/tempdir) |
||||
|
||||
global tcl_platform env |
||||
|
||||
set attempdirs [list] |
||||
|
||||
foreach tmp {TMPDIR TEMP TMP} { |
||||
if { [info exists env($tmp)] } { |
||||
lappend attempdirs $env($tmp) |
||||
} |
||||
} |
||||
|
||||
switch $tcl_platform(platform) { |
||||
windows { |
||||
lappend attempdirs "C:\\TEMP" "C:\\TMP" "\\TEMP" "\\TMP" |
||||
} |
||||
macintosh { |
||||
set tmpdir $env(TRASH_FOLDER) ;# a better place? |
||||
} |
||||
default { |
||||
lappend attempdirs \ |
||||
[file join / tmp] \ |
||||
[file join / var tmp] \ |
||||
[file join / usr tmp] |
||||
} |
||||
} |
||||
|
||||
lappend attempdirs [pwd] |
||||
|
||||
foreach tmp $attempdirs { |
||||
if { [file isdirectory $tmp] && [file writable $tmp] } { |
||||
return [file normalize $tmp] |
||||
} |
||||
} |
||||
|
||||
# Fail if nothing worked. |
||||
return -code error "Unable to determine a proper directory for temporary files" |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Ready |
||||
|
||||
package provide platform::shell 1.1.4 |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,595 @@
|
||||
#package require logger |
||||
|
||||
package provide shellthread [namespace eval shellthread { |
||||
variable version |
||||
set version 1.6 |
||||
}] |
||||
|
||||
|
||||
package require Thread |
||||
|
||||
namespace eval shellthread { |
||||
|
||||
proc iso8601 {{tsmicros ""}} { |
||||
if {$tsmicros eq ""} { |
||||
set tsmicros [clock micros] |
||||
} else { |
||||
set microsnow [clock micros] |
||||
if {[string length $tsmicros] != [string length $microsnow]} { |
||||
error "iso8601 requires 'clock micros' or empty string to create timestamp" |
||||
} |
||||
} |
||||
set seconds [expr {$tsmicros / 1000000}] |
||||
return [clock format $seconds -format "%Y-%m-%d_%H-%M-%S"] |
||||
} |
||||
} |
||||
|
||||
namespace eval shellthread::worker { |
||||
variable settings |
||||
variable sysloghost_port |
||||
variable sock |
||||
variable logfile "" |
||||
variable fd |
||||
variable client_ids [list] |
||||
variable ts_start_micros |
||||
variable errorlist [list] |
||||
variable inpipe "" |
||||
|
||||
proc bgerror {args} { |
||||
variable errorlist |
||||
lappend errorlist $args |
||||
} |
||||
proc send_errors_now {tidcli} { |
||||
variable errorlist |
||||
thread::send -async $tidcli [list shellthread::manager::report_worker_errors [list worker_tid [thread::id] errors $errorlist]] |
||||
} |
||||
proc add_client_tid {tidcli} { |
||||
variable client_ids |
||||
if {$tidcli ni $client_ids} { |
||||
lappend client_ids $tidcli |
||||
} |
||||
} |
||||
proc init {tidclient start_m settingsdict} { |
||||
variable sysloghost_port |
||||
variable logfile |
||||
variable settings |
||||
interp bgerror {} shellthread::worker::bgerror |
||||
package require overtype |
||||
variable client_ids |
||||
variable ts_start_micros |
||||
lappend client_ids $tidclient |
||||
set ts_start_micros $start_m |
||||
|
||||
set defaults [list -raw 0 -file "" -syslog "" -direction out] |
||||
set settings [dict merge $defaults $settingsdict] |
||||
|
||||
set syslog [dict get $settings -syslog] |
||||
if {[string length $syslog]} { |
||||
lassign [split $syslog :] s_host s_port |
||||
set sysloghost_port [list $s_host $s_port] |
||||
} else { |
||||
set sysloghost_port "" |
||||
} |
||||
if {[catch {package require udp} errm]} { |
||||
#disable rather than bomb and interfere with any -file being written |
||||
set sysloghost_port "" |
||||
} |
||||
|
||||
set logfile [dict get $settings -file] |
||||
} |
||||
|
||||
proc start_pipe_read {source readchan args} { |
||||
#assume 1 inpipe for now |
||||
variable inpipe |
||||
variable sysloghost_port |
||||
variable logfile |
||||
set defaults [dict create -buffering \uFFFF ] |
||||
set opts [dict merge $defaults $args] |
||||
if {[dict exists $opts -readbuffering]} { |
||||
set readbuffering [dict get $opts -readbuffering] |
||||
} else { |
||||
if {[dict get $opts -buffering] eq "\uFFFF"} { |
||||
#get buffering setting from the channel as it was set prior to thread::transfer |
||||
set readbuffering [chan configure $readchan -buffering] |
||||
} else { |
||||
set readbuffering [dict get $opts -buffering] |
||||
chan configure $readchan -buffering $readbuffering |
||||
} |
||||
} |
||||
if {[dict exists $opts -writebuffering]} { |
||||
set writebuffering [dict get $opts -writebuffering] |
||||
} else { |
||||
if {[dict get $opts -buffering] eq "\uFFFF"} { |
||||
set writebuffering line |
||||
#set writebuffering [chan configure $writechan -buffering] |
||||
} else { |
||||
set writebuffering [dict get $opts -buffering] |
||||
#can configure $writechan -buffering $writebuffering |
||||
} |
||||
} |
||||
|
||||
chan configure $readchan -translation lf |
||||
|
||||
if {$readchan ni [chan names]} { |
||||
error "shellthread::worker::start_pipe_read - inpipe not configured. Use shellthread::manager::set_pipe_read_from_client to thread::transfer the pipe end" |
||||
} |
||||
set inpipe $readchan |
||||
#::shellthread::worker::log $inpipe 0 - $source - info "START PIPE READ HELLO\n" line |
||||
chan configure $readchan -blocking 0 |
||||
#::shellthread::worker::log $inpipe 0 - $source - info "START PIPE READ HELLO2 readbuffering: $readbuffering syslog $sysloghost_port filename $logfile" line |
||||
|
||||
set waitvar ::shellthread::worker::wait($inpipe,[clock micros]) |
||||
chan event $readchan readable [list apply {{chan source waitfor readbuffering writebuffering} { |
||||
if {$readbuffering eq "line"} { |
||||
set chunksize [chan gets $chan chunk] |
||||
if {$chunksize >= 0} { |
||||
if {![chan eof $chan]} { |
||||
::shellthread::worker::log pipe 0 - $source - info $chunk\n $writebuffering |
||||
} else { |
||||
::shellthread::worker::log pipe 0 - $source - info $chunk $writebuffering |
||||
} |
||||
} |
||||
} else { |
||||
set chunk [chan read $chan] |
||||
::shellthread::worker::log pipe 0 - $source - info $chunk $writebuffering |
||||
} |
||||
if {[chan eof $chan]} { |
||||
chan event $chan readable {} |
||||
set $waitfor "pipe" |
||||
chan close $chan |
||||
} |
||||
}} $readchan $source $waitvar $readbuffering $writebuffering] |
||||
#::shellthread::worker::log $inpipe 0 - $source - info "START PIPE READ HELLO3 vwaiting on $waitvar\n" line |
||||
vwait $waitvar |
||||
} |
||||
|
||||
proc start_pipe_write {source writechan args} { |
||||
variable outpipe |
||||
set defaults [dict create -buffering \uFFFF ] |
||||
set opts [dict merge $defaults $args] |
||||
|
||||
#todo! |
||||
set readchan stdin |
||||
|
||||
if {[dict exists $opts -readbuffering]} { |
||||
set readbuffering [dict get $opts -readbuffering] |
||||
} else { |
||||
if {[dict get $opts -buffering] eq "\uFFFF"} { |
||||
set readbuffering [chan configure $readchan -buffering] |
||||
} else { |
||||
set readbuffering [dict get $opts -buffering] |
||||
chan configure $readchan -buffering $readbuffering |
||||
} |
||||
} |
||||
if {[dict exists $opts -writebuffering]} { |
||||
set writebuffering [dict get $opts -writebuffering] |
||||
} else { |
||||
if {[dict get $opts -buffering] eq "\uFFFF"} { |
||||
#nothing explicitly set - take from transferred channel |
||||
set writebuffering [chan configure $writechan -buffering] |
||||
} else { |
||||
set writebuffering [dict get $opts -buffering] |
||||
can configure $writechan -buffering $writebuffering |
||||
} |
||||
} |
||||
|
||||
if {$writechan ni [chan names]} { |
||||
error "shellthread::worker::start_pipe_write - outpipe not configured. Use shellthread::manager::set_pipe_write_to_client to thread::transfer the pipe end" |
||||
} |
||||
set outpipe $writechan |
||||
chan configure $readchan -blocking 0 |
||||
chan configure $writechan -blocking 0 |
||||
set waitvar ::shellthread::worker::wait($outpipe,[clock micros]) |
||||
|
||||
chan event $readchan readable [list apply {{chan writechan source waitfor readbuffering} { |
||||
if {$readbuffering eq "line"} { |
||||
set chunksize [chan gets $chan chunk] |
||||
if {$chunksize >= 0} { |
||||
if {![chan eof $chan]} { |
||||
puts $writechan $chunk |
||||
} else { |
||||
puts -nonewline $writechan $chunk |
||||
} |
||||
} |
||||
} else { |
||||
set chunk [chan read $chan] |
||||
puts -nonewline $writechan $chunk |
||||
} |
||||
if {[chan eof $chan]} { |
||||
chan event $chan readable {} |
||||
set $waitfor "pipe" |
||||
chan close $writechan |
||||
if {$chan ne "stdin"} { |
||||
chan close $chan |
||||
} |
||||
} |
||||
}} $readchan $writechan $source $waitvar $readbuffering] |
||||
|
||||
vwait $waitvar |
||||
} |
||||
|
||||
|
||||
proc _initsock {} { |
||||
variable sysloghost_port |
||||
variable sock |
||||
if {[string length $sysloghost_port]} { |
||||
if {[catch {fconfigure $sock} state]} { |
||||
set sock [udp_open] |
||||
fconfigure $sock -buffering none -translation binary |
||||
fconfigure $sock -remote $sysloghost_port |
||||
} |
||||
} |
||||
} |
||||
proc _reconnect {} { |
||||
variable sock |
||||
catch {close $sock} |
||||
_initsock |
||||
return [fconfigure $sock] |
||||
} |
||||
|
||||
proc send_info {client_tid ts_sent source msg} { |
||||
set ts_received [clock micros] |
||||
set lag_micros [expr {$ts_received - $ts_sent}] |
||||
set lag [expr {$lag_micros / 1000000.0}] ;#lag as x.xxxxxx seconds |
||||
|
||||
log $client_tid $ts_sent $lag $source - info $msg line 1 |
||||
} |
||||
proc log {client_tid ts_sent lag source service level msg writebuffering {islog 0}} { |
||||
variable sock |
||||
variable fd |
||||
variable sysloghost_port |
||||
variable logfile |
||||
variable settings |
||||
|
||||
set logchunk $msg |
||||
|
||||
if {![dict get $settings -raw]} { |
||||
set tail_crlf 0 |
||||
set tail_lf 0 |
||||
set tail_cr 0 |
||||
#for cooked - always remove the trailing newline before splitting.. |
||||
# |
||||
#note that if we got our data from reading a non-line-buffered binary channel - then this naive line splitting will not split neatly for mixed line-endings. |
||||
# |
||||
#Possibly not critical as cooked is for logging and we are still preserving all \r and \n chars - but review and consider implementing a better split |
||||
#but add it back exactly as it was afterwards |
||||
#we can always split on \n - and any adjacent \r will be preserved in the rejoin |
||||
set lastchar [string range $logchunk end end] |
||||
if {[string range $logchunk end-1 end] eq "\r\n"} { |
||||
set tail_crlf 1 |
||||
set logchunk [string range $logchunk 0 end-2] |
||||
} else { |
||||
if {$lastchar eq "\n"} { |
||||
set tail_lf 1 |
||||
set logchunk [string range $logchunk 0 end-1] |
||||
} elseif {$lastchar eq "\r"} { |
||||
#\r line-endings are obsolete..and unlikely... and ugly as they can hide characters on the console. but we'll pass through anyway. |
||||
set tail_cr 1 |
||||
set logchunk [string range $logchunk 0 end-1] |
||||
} else { |
||||
#possibly a single line with no linefeed.. or has linefeeds only in the middle |
||||
} |
||||
} |
||||
|
||||
if {$ts_sent != 0} { |
||||
set micros [lindex [split [expr {$ts_sent / 1000000.0}] .] end] |
||||
set time_info [::shellthread::iso8601 $ts_sent].$micros |
||||
#set time_info "${time_info}+$lag" |
||||
set lagfp "+[format %f $lag]" |
||||
} else { |
||||
#from pipe - no ts_sent/lag info available |
||||
set time_info "" |
||||
set lagfp "" |
||||
} |
||||
|
||||
set idtail [string range $client_tid end-8 end] ;#enough for display purposes id - mostly zeros anyway |
||||
set col0 [string repeat " " 9] |
||||
set col1 [string repeat " " 27] |
||||
set col2 [string repeat " " 11] |
||||
set col3 [string repeat " " 20] |
||||
#do not columnize the final data column or append to tail - or we could muck up the crlf integrity |
||||
|
||||
lassign [list [overtype::left $col0 $idtail] [overtype::left $col1 $time_info] [overtype::left $col2 $lagfp] [overtype::left $col3 $source]] c0 c1 c2 c3 |
||||
|
||||
#split on \n no matter the actual line-ending in use |
||||
#shouldn't matter as long as we don't add anything at the end of the line other than the raw data |
||||
#ie - don't quote or add spaces |
||||
set lines [split $logchunk \n] |
||||
|
||||
set i 1 |
||||
set outlines [list] |
||||
foreach ln $lines { |
||||
if {$i == 1} { |
||||
lappend outlines "$c0 $c1 $c2 $c3 $ln" |
||||
} else { |
||||
lappend outlines "$c0 $c1 $col2 $c3 $ln" |
||||
} |
||||
incr i |
||||
} |
||||
if {$tail_lf} { |
||||
set logchunk "[join $outlines \n]\n" |
||||
} elseif {$tail_crlf} { |
||||
set logchunk "[join $outlines \r\n]\r\n" |
||||
} elseif {$tail_cr} { |
||||
set logchunk "[join $outlines \r]\r" |
||||
} else { |
||||
#no trailing linefeed |
||||
set logchunk [join $outlines \n] |
||||
|
||||
} |
||||
|
||||
#set logchunk "[overtype::left $col0 $idtail] [overtype::left $col1 $time_info] [overtype::left $col2 "+$lagfp"] [overtype::left $col3 $source] $msg" |
||||
} |
||||
|
||||
if {[string length $sysloghost_port]} { |
||||
_initsock |
||||
catch {puts -nonewline $sock $logchunk} |
||||
} |
||||
#todo - sockets etc? |
||||
if {[string length $logfile]} { |
||||
#todo - setting to maintain open filehandle and reduce io. |
||||
# possible settings for buffersize - and maybe logrotation, although this could be left to client |
||||
#for now - default to safe option of open/close each write despite the overhead. |
||||
set fd [open $logfile a] |
||||
chan configure $fd -translation auto -buffering $writebuffering |
||||
#whether line buffered or not - by now our logchunk includes newlines |
||||
puts -nonewline $fd $logchunk |
||||
close $fd |
||||
} |
||||
} |
||||
|
||||
# - withdraw just this client |
||||
proc finish {tidclient} { |
||||
variable client_ids |
||||
if {($tidclient in $clientids) && ([llength $clientids] == 1)} { |
||||
terminate $tidclient |
||||
} else { |
||||
set posn [lsearch $client_ids $tidclient] |
||||
set client_ids [lreplace $clientids $posn $posn] |
||||
} |
||||
} |
||||
|
||||
#allow any client to terminate |
||||
proc terminate {tidclient} { |
||||
variable sock |
||||
variable client_ids |
||||
if {$tidclient in $client_ids} { |
||||
catch {close $sock} |
||||
set client_ids [list] |
||||
return 1 |
||||
} else { |
||||
return 0 |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
|
||||
namespace eval shellthread::manager { |
||||
variable workers [dict create] |
||||
variable worker_errors [list] |
||||
|
||||
variable log_threads |
||||
|
||||
#new datastructure regarding workers and sourcetags required. |
||||
#one worker can service multiple sourcetags - but each sourcetag may be used by multiple threads too. |
||||
#generally each thread will use a specific sourcetag - but we may have pools doing similar things which log to same destination. |
||||
# |
||||
#As a convention we may use a sourcetag for the thread which started the worker that isn't actually used for logging - but as a common target for joins |
||||
#If the thread which started the thread calls leave_worker with that 'primary' sourcetag it means others won't be able to use that target - which seems reasonable. |
||||
#If another thread want's to maintain joinability beyond the span provided by the starting client, |
||||
#it can join with both the primary tag and a tag it will actually use for logging. |
||||
#A thread can join the logger with any existingtag - not just the 'primary' |
||||
#(which is arbitrary anyway. It will usually be the first in the list - but may be unsubscribed by clients and disappear) |
||||
proc join_worker {client_tid existingtag sourcetaglist} { |
||||
#todo - allow a source to piggyback on existing worker by referencing one of the sourcetags already using the worker |
||||
} |
||||
proc leave_worker {client_tid sourcetaglist} { |
||||
#todo |
||||
#unsub this client_tid from the sourcetags in the sourcetaglist. if no more client_tids exist for sourcetag, remove sourcetag, |
||||
#if no more sourcetags - close worker |
||||
} |
||||
#it is up to caller to use a unique sourcetag (e.g by prefixing with own thread::id etc) |
||||
# This allows multiple threads to more easily write to the same named sourcetag if necessary |
||||
# todo - change sourcetag for a list of tags which will be handled by the same thread. e.g for multiple threads logging to same file |
||||
# |
||||
# todo - some protection mechanism for case where target is a file to stop creation of multiple worker threads writing to same file. |
||||
# Even if we use open fd,close fd wrapped around writes.. it is probably undesirable to have multiple threads with same target |
||||
# On the other hand socket targets such as UDP can happily be written to by multiple threads. |
||||
# For now the mechanism is that a call to new_worker (rename to open_worker?) will join the same thread if a sourcetag matches.. |
||||
# but, as sourcetags can get removed(unsubbed via leave_worker) this doesn't guarantee two threads with same -file settings won't fight. |
||||
# Also.. the settingsdict is ignored when joining with a tag that exists.. this is problematic.. e.g logrotation where previous file still being written by existing worker |
||||
# todo - rename 'sourcetag' concept to 'targettag' ?? the concept is a mixture of both.. it is somewhat analagous to a syslog 'facility' |
||||
# probably new_worker should disallow auto-joining and we allow different workers to handle same tags simultaneously to support overlap during logrotation etc. |
||||
proc new_worker {sourcetaglist {settingsdict {}}} { |
||||
variable workers |
||||
set ts_start [clock micros] |
||||
set tidclient [thread::id] |
||||
set sourcetag [lindex $sourcetaglist 0] ;#todo - use all |
||||
|
||||
if {[dict exists $workers $sourcetag]} { |
||||
set winfo [dict get $workers $sourcetag] |
||||
if {[thread::exists [dict get $winfo tid]]} { |
||||
#add our client-info to existing worker thread |
||||
dict lappend winfo list_client_tids $tidclient |
||||
dict set workers $sourcetag $winfo ;#writeback |
||||
return [dict get $winfo tid] |
||||
} |
||||
} |
||||
#set ts_start [::shellthread::iso8601] |
||||
set tidworker [thread::create -preserved] |
||||
set init_script [string map [list %ts_start% $ts_start %mp% [tcl::tm::list] %ap% $::auto_path %tidcli% $tidclient %sd% $settingsdict] { |
||||
#set tclbase [file dirname [file dirname [info nameofexecutable]]] |
||||
#set tcllib $tclbase/lib |
||||
#if {$tcllib ni $::auto_path} { |
||||
# lappend ::auto_path $tcllib |
||||
#} |
||||
|
||||
set ::settingsinfo [dict create %sd%] |
||||
#if the executable running things is something like a tclkit, |
||||
# then it's likely we will need to use the caller's auto_path and tcl::tm::list to find things |
||||
#The caller can tune the thread's package search by providing a settingsdict |
||||
if {![dict exists $::settingsinfo tcl_tm_list]} { |
||||
tcl::tm::add %mp% |
||||
} else { |
||||
tcl::tm::remove {*}[tcl::tm::list] |
||||
tcl::tm::add {*}[dict get $::settingsinfo tcl_tm_list] |
||||
} |
||||
if {![dict exists $::settingsinfo auto_path]} { |
||||
set ::auto_path [list %ap%] |
||||
} else { |
||||
set ::auto_path [dict get $::settingsinfo auto_path] |
||||
} |
||||
|
||||
package require Thread |
||||
package require shellthread |
||||
if {![catch {::shellthread::worker::init %tidcli% %ts_start% $::settingsinfo} errmsg]} { |
||||
unset ::settingsinfo |
||||
set ::shellthread_init "ok" |
||||
} else { |
||||
unset ::settingsinfo |
||||
set ::shellthread_init "err $errmsg" |
||||
} |
||||
}] |
||||
|
||||
thread::send -async $tidworker $init_script |
||||
#thread::send $tidworker $init_script |
||||
set winfo [dict create tid $tidworker list_client_tids [list $tidclient] ts_start $ts_start ts_end_list [list]] |
||||
dict set workers $sourcetag $winfo |
||||
return $tidworker |
||||
} |
||||
|
||||
proc set_pipe_read_from_client {tag_pipename worker_tid rchan args} { |
||||
variable workers |
||||
if {![dict exists $workers $tag_pipename]} { |
||||
error "workerthread::manager::set_pipe_read_from_client source/pipename $tag_pipename not found" |
||||
} |
||||
set match_worker_tid [dict get $workers $tag_pipename tid] |
||||
if {$worker_tid ne $match_worker_tid} { |
||||
error "workerthread::manager::set_pipe_read_from_client source/pipename $tag_pipename workert_tid mismatch '$worker_tid' vs existing:'$match_worker_tid'" |
||||
} |
||||
#buffering set during channel creation will be preserved on thread::transfer |
||||
thread::transfer $worker_tid $rchan |
||||
#start_pipe_read will vwait - so we have to send async |
||||
thread::send -async $worker_tid [list ::shellthread::worker::start_pipe_read $tag_pipename $rchan] |
||||
#client may start writing immediately - but presumably it will buffer in fifo2 |
||||
} |
||||
|
||||
proc set_pipe_write_to_client {tag_pipename worker_tid wchan args} { |
||||
variable workers |
||||
if {![dict exists $workers $tag_pipename]} { |
||||
error "workerthread::manager::set_pipe_write_to_client pipename $tag_pipename not found" |
||||
} |
||||
set match_worker_tid [dict get $workers $tag_pipename tid] |
||||
if {$worker_tid ne $match_worker_tid} { |
||||
error "workerthread::manager::set_pipe_write_to_client pipename $tag_pipename workert_tid mismatch '$worker_tid' vs existing:'$match_worker_tid'" |
||||
} |
||||
#buffering set during channel creation will be preserved on thread::transfer |
||||
thread::transfer $worker_tid $wchan |
||||
thread::send -async $worker_tid [list ::shellthread::worker::start_pipe_write $tag_pipename $wchan] |
||||
} |
||||
|
||||
proc write_log {source msg args} { |
||||
variable workers |
||||
set ts_micros_sent [clock micros] |
||||
set defaults [list -async 1 -level info] |
||||
set opts [dict merge $defaults $args] |
||||
|
||||
if {[dict exists $workers $source]} { |
||||
set tidworker [dict get $workers $source tid] |
||||
if {![thread::exists $tidworker]} { |
||||
set tidworker [new_worker $source] |
||||
} |
||||
} else { |
||||
#auto create with no requirement to call new_worker.. warn? |
||||
set tidworker [new_worker $source] |
||||
} |
||||
set client_tid [thread::id] |
||||
if {[dict get $opts -async]} { |
||||
thread::send -async $tidworker [list ::shellthread::worker::send_info $client_tid $ts_micros_sent $source $msg] |
||||
} else { |
||||
thread::send $tidworker [list ::shellthread::worker::send_info $client_tid $ts_micros_sent $source $msg] |
||||
} |
||||
} |
||||
proc report_worker_errors {errdict} { |
||||
variable workers |
||||
set reporting_tid [dict get $errdict worker_tid] |
||||
dict for {src srcinfo} $workers { |
||||
if {[dict get $srcinfo tid] eq $reporting_tid} { |
||||
dict set srcinfo errors [dict get $errdict errors] |
||||
dict set workers $src $srcinfo ;#writeback updated |
||||
break |
||||
} |
||||
} |
||||
} |
||||
proc close_worker {source {timeout 2500}} { |
||||
variable workers |
||||
variable worker_errors |
||||
set ts_now [clock micros] |
||||
#puts stderr "close_worker $source" |
||||
if {[dict exists $workers $source]} { |
||||
set tidworker [dict get $workers $source tid] |
||||
set ts_end_list [dict get $workers $source ts_end_list] |
||||
if {[llength $ts_end_list]} { |
||||
set last_end_ts [lindex $ts_end_list end] |
||||
if {[expr {(($tsnow - $last_end_ts) / 1000) >= $timeout}]} { |
||||
lappend ts_end_list $ts_now |
||||
dict set workers $source ts_end_list $ts_end_list |
||||
} else { |
||||
#existing close in progress.. assume it will work |
||||
return |
||||
} |
||||
} |
||||
|
||||
if {[thread::exists $tidworker]} { |
||||
#puts stderr "shellthread::manager::close_worker: thread $tidworker for source $source still running.. terminating" |
||||
set timeoutarr($source) 0 |
||||
after $timeout [list set timeoutarr($source) 2] |
||||
|
||||
thread::send -async $tidworker [list shellthread::worker::send_errors_now [thread::id]] |
||||
thread::send -async $tidworker [list shellthread::worker::terminate [thread::id]] timeoutarr($source) |
||||
|
||||
#thread::send -async $tidworker [string map [list %tidclient% [thread::id]] { |
||||
# shellthread::worker::terminate %tidclient% |
||||
#}] timeoutarr($source) |
||||
|
||||
vwait timeoutarr($source) |
||||
#puts stderr "shellthread::manager::close_worker: thread $tidworker for source $source DONE1" |
||||
|
||||
thread::release $tidworker |
||||
#puts stderr "shellthread::manager::close_worker: thread $tidworker for source $source DONE2" |
||||
if {[dict exists $workers $source errors]} { |
||||
set errlist [dict get $workers $source errors] |
||||
if {[llength $errlist]} { |
||||
lappend worker_errors [list $source [dict get $workers $source]] |
||||
} |
||||
} |
||||
dict unset workers $source |
||||
} |
||||
} |
||||
#puts stdout "close_worker $source - end" |
||||
} |
||||
|
||||
#worker errors only available for a source after close_worker called on that source |
||||
#It is possible for there to be multiple entries for a source because new_worker can be called multiple times with same sourcetag, |
||||
# e.g if a thread |
||||
proc get_and_clear_errors {source} { |
||||
variable worker_errors |
||||
set source_errors [lsearch -all -inline -index 0 $worker_errors $source] |
||||
set worker_errors [lsearch -all -inline -index 0 -not $worker_errors $source] |
||||
return $source_errors |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
|
||||
package ifneeded app-punk 1.0 [list source [file join $dir repl.tcl]] |
||||
|
@ -0,0 +1,820 @@
|
||||
#temp |
||||
package provide app-punk 1.0 |
||||
|
||||
set stdin_info [chan configure stdin] |
||||
if {[dict exists $stdin_info -inputmode]} { |
||||
#this is the only way I currently know to detect console on windows.. doesn't work on Alma linux. |
||||
# tcl_interactive used by repl to determine if stderr output prompt to be printed. |
||||
# (that way, piping commands into stdin should not produce prompts for each command) |
||||
set tcl_interactive 1 |
||||
} |
||||
#however, the -mode option only seems to appear on linux when a terminal exists.. |
||||
if {[dict exists $stdin_info -mode]} { |
||||
set tcl_interactive 1 |
||||
} |
||||
|
||||
#give up for now |
||||
set tcl_interactive 1 |
||||
|
||||
proc todo {} { |
||||
puts "tcl History" |
||||
|
||||
|
||||
} |
||||
tcl::tm::add [pwd]/modules |
||||
|
||||
package require shellfilter |
||||
package require Thread |
||||
set outdevice [shellfilter::stack::new punkout -settings {-tag "punkout" -buffering none -raw 1 -syslog 127.0.0.1:514 -file "c:/repo/jn/shellspy/logs/repl-exec-stdout.txt"}] |
||||
set out [dict get $outdevice localchan] |
||||
set errdevice [shellfilter::stack::new punkerr -settings {-tag "punkerr" -buffering none -raw 1 -syslog 127.0.0.1:514 -file "c:/repo/jn/shellspy/logs/repl-exec-stderr.txt"}] |
||||
set err [dict get $errdevice localchan] |
||||
# |
||||
#set indevice [shellfilter::stack::new commandin -settings {-tag "commandin" -readbuffering line -writebuffering none -raw 1 -direction in}] |
||||
#set program_read_stdin_pipe [dict get $indevice localchan] |
||||
|
||||
|
||||
|
||||
# unknown -- |
||||
# This procedure is called when a Tcl command is invoked that doesn't |
||||
# exist in the interpreter. It takes the following steps to make the |
||||
# command available: |
||||
# |
||||
# 1. See if the autoload facility can locate the command in a |
||||
# Tcl script file. If so, load it and execute it. |
||||
# 2. If the command was invoked interactively at top-level: |
||||
# (a) see if the command exists as an executable UNIX program. |
||||
# If so, "exec" the command. |
||||
# (b) see if the command requests csh-like history substitution |
||||
# in one of the common forms !!, !<number>, or ^old^new. If |
||||
# so, emulate csh's history substitution. |
||||
# (c) see if the command is a unique abbreviation for another |
||||
# command. If so, invoke the command. |
||||
# |
||||
# Arguments: |
||||
# args - A list whose elements are the words of the original |
||||
# command, including the command name. |
||||
|
||||
proc unknown args { |
||||
variable ::tcl::UnknownPending |
||||
global auto_noexec auto_noload env tcl_interactive errorInfo errorCode |
||||
|
||||
if {[info exists errorInfo]} { |
||||
set savedErrorInfo $errorInfo |
||||
} |
||||
if {[info exists errorCode]} { |
||||
set savedErrorCode $errorCode |
||||
} |
||||
|
||||
set name [lindex $args 0] |
||||
if {![info exists auto_noload]} { |
||||
# |
||||
# Make sure we're not trying to load the same proc twice. |
||||
# |
||||
if {[info exists UnknownPending($name)]} { |
||||
return -code error "self-referential recursion\ |
||||
in \"unknown\" for command \"$name\"" |
||||
} |
||||
set UnknownPending($name) pending |
||||
set ret [catch { |
||||
auto_load $name [uplevel 1 {::namespace current}] |
||||
} msg opts] |
||||
unset UnknownPending($name) |
||||
if {$ret != 0} { |
||||
dict append opts -errorinfo "\n (autoloading \"$name\")" |
||||
return -options $opts $msg |
||||
} |
||||
if {![array size UnknownPending]} { |
||||
unset UnknownPending |
||||
} |
||||
if {$msg} { |
||||
if {[info exists savedErrorCode]} { |
||||
set ::errorCode $savedErrorCode |
||||
} else { |
||||
unset -nocomplain ::errorCode |
||||
} |
||||
if {[info exists savedErrorInfo]} { |
||||
set errorInfo $savedErrorInfo |
||||
} else { |
||||
unset -nocomplain errorInfo |
||||
} |
||||
set code [catch {uplevel 1 $args} msg opts] |
||||
if {$code == 1} { |
||||
# |
||||
# Compute stack trace contribution from the [uplevel]. |
||||
# Note the dependence on how Tcl_AddErrorInfo, etc. |
||||
# construct the stack trace. |
||||
# |
||||
set errInfo [dict get $opts -errorinfo] |
||||
set errCode [dict get $opts -errorcode] |
||||
set cinfo $args |
||||
if {[string length [encoding convertto utf-8 $cinfo]] > 150} { |
||||
set cinfo [string range $cinfo 0 150] |
||||
while {[string length [encoding convertto utf-8 $cinfo]] > 150} { |
||||
set cinfo [string range $cinfo 0 end-1] |
||||
} |
||||
append cinfo ... |
||||
} |
||||
set tail "\n (\"uplevel\" body line 1)\n invoked\ |
||||
from within\n\"uplevel 1 \$args\"" |
||||
set expect "$msg\n while executing\n\"$cinfo\"$tail" |
||||
if {$errInfo eq $expect} { |
||||
# |
||||
# The stack has only the eval from the expanded command |
||||
# Do not generate any stack trace here. |
||||
# |
||||
dict unset opts -errorinfo |
||||
dict incr opts -level |
||||
return -options $opts $msg |
||||
} |
||||
# |
||||
# Stack trace is nested, trim off just the contribution |
||||
# from the extra "eval" of $args due to the "catch" above. |
||||
# |
||||
set last [string last $tail $errInfo] |
||||
if {$last + [string length $tail] != [string length $errInfo]} { |
||||
# Very likely cannot happen |
||||
return -options $opts $msg |
||||
} |
||||
set errInfo [string range $errInfo 0 $last-1] |
||||
set tail "\"$cinfo\"" |
||||
set last [string last $tail $errInfo] |
||||
if {$last < 0 || $last + [string length $tail] != [string length $errInfo]} { |
||||
return -code error -errorcode $errCode \ |
||||
-errorinfo $errInfo $msg |
||||
} |
||||
set errInfo [string range $errInfo 0 $last-1] |
||||
set tail "\n invoked from within\n" |
||||
set last [string last $tail $errInfo] |
||||
if {$last + [string length $tail] == [string length $errInfo]} { |
||||
return -code error -errorcode $errCode \ |
||||
-errorinfo [string range $errInfo 0 $last-1] $msg |
||||
} |
||||
set tail "\n while executing\n" |
||||
set last [string last $tail $errInfo] |
||||
if {$last + [string length $tail] == [string length $errInfo]} { |
||||
return -code error -errorcode $errCode \ |
||||
-errorinfo [string range $errInfo 0 $last-1] $msg |
||||
} |
||||
return -options $opts $msg |
||||
} else { |
||||
dict incr opts -level |
||||
return -options $opts $msg |
||||
} |
||||
} |
||||
} |
||||
#set isrepl [expr {[file tail [file rootname [info script]]] eq "repl"}] |
||||
set isrepl $::repl::running ;#may not be reading though |
||||
if {$isrepl} { |
||||
#set ::tcl_interactive 1 |
||||
} |
||||
if {$isrepl || (([info level] == 1) && (([info script] eq "" ) ) |
||||
&& ([info exists tcl_interactive] && $tcl_interactive))} { |
||||
if {![info exists auto_noexec]} { |
||||
set new [auto_execok $name] |
||||
if {$new ne ""} { |
||||
set redir "" |
||||
if {[namespace which -command console] eq ""} { |
||||
set redir ">&@stdout <@stdin" |
||||
} |
||||
|
||||
|
||||
#experiment todo - use twapi and named pipes |
||||
#twapi::namedpipe_server {\\.\pipe\something} |
||||
#Then override tcl 'exec' and replace all stdout/stderr/stdin with our fake ones |
||||
#These can be stacked with shellfilter and operate as OS handles - which we can't do with fifo2 etc |
||||
# |
||||
|
||||
|
||||
# 'script' command to fake a tty |
||||
# note that we lose the exit code from the underlying command by using 'script' if we call shellfilter::run without -e option to script |
||||
set scr [auto_execok script] |
||||
if {$scr ne ""} { |
||||
if {[string first " " $new] > 0} { |
||||
set c1 $name |
||||
} else { |
||||
set c1 $new |
||||
} |
||||
#set scriptrun "( $c1 [lrange $args 1 end] )" |
||||
|
||||
set scriptrun_commandlist [shellfilter::get_scriptrun_from_cmdlist_dquote_if_not $args] |
||||
|
||||
if 0 { |
||||
set scriptrun "( $c1 " |
||||
foreach a [lrange $args 1 end] { |
||||
if {[string first " " $a] > 0} { |
||||
#append scriptrun "\"$a\"" |
||||
append scriptrun $a |
||||
} else { |
||||
append scriptrun $a |
||||
} |
||||
append scriptrun " " |
||||
} |
||||
append scriptrun " )" |
||||
} |
||||
#------------------------------------- |
||||
if 0 { |
||||
package require string::token::shell |
||||
set shellparts [string token shell -indices $args] |
||||
|
||||
set scriptrun "( $c1 " |
||||
foreach info [lrange $shellparts 1 end] { |
||||
set type [lindex $info 0] |
||||
if {$type eq "D:QUOTED"} { |
||||
append scriptrun "\"" |
||||
append scriptrun [lindex $info 3] |
||||
append scriptrun "\"" |
||||
} elseif {$type eq "S:QUOTED"} { |
||||
append scriptrun "'" |
||||
append scriptrun [lindex $info 3] |
||||
append scriptrun "'" |
||||
} elseif {$type eq "PLAIN"} { |
||||
append scriptrun [lindex $info 3] |
||||
} else { |
||||
error "Can't interpret '$args' with sh-like syntax" |
||||
} |
||||
append scriptrun " " |
||||
} |
||||
append scriptrun " )" |
||||
} |
||||
|
||||
#------------------------------------- |
||||
#puts stderr ">>>scriptrun_commandlist: $scriptrun_commandlist" |
||||
|
||||
#uplevel 1 [list ::catch \ |
||||
[list ::shellfilter::run [list $scr -q -e -c $scriptrun /dev/null] -teehandle punk -inbuffering line -outbuffering none ] \ |
||||
::tcl::UnknownResult ::tcl::UnknownOptions] |
||||
|
||||
uplevel 1 [list ::catch \ |
||||
[list ::shellfilter::run $scriptrun_commandlist -teehandle punk -inbuffering line -outbuffering none ] \ |
||||
::tcl::UnknownResult ::tcl::UnknownOptions] |
||||
|
||||
|
||||
|
||||
#puts stderr "script result $::tcl::UnknownOptions $::tcl::UnknownResult" |
||||
} else { |
||||
uplevel 1 [list ::catch \ |
||||
[list ::shellfilter::run [concat $new [lrange $args 1 end]] -teehandle punk -inbuffering line -outbuffering none ] \ |
||||
::tcl::UnknownResult ::tcl::UnknownOptions] |
||||
} |
||||
if {[string trim $::tcl::UnknownResult] ne "exitcode 0"} { |
||||
dict set ::tcl::UnknownOptions -code error |
||||
set ::tcl::UnknownResult "Non-zero exit code from command '$c1 [lrange $args 1 end]' $::tcl::UnknownResult" |
||||
} else { |
||||
#no point returning "exitcode 0" if that's the only non-error return. |
||||
#It is misleading. Better to return empty string. |
||||
set ::tcl::UnknownResult "" |
||||
} |
||||
|
||||
|
||||
#uplevel 1 [list ::catch \ |
||||
# [concat exec $redir $new [lrange $args 1 end]] \ |
||||
# ::tcl::UnknownResult ::tcl::UnknownOptions] |
||||
|
||||
#puts "===exec with redir:$redir $::tcl::UnknownResult ==" |
||||
dict incr ::tcl::UnknownOptions -level |
||||
return -options $::tcl::UnknownOptions $::tcl::UnknownResult |
||||
} |
||||
} |
||||
if {$name eq "!!"} { |
||||
set newcmd [history event] |
||||
} elseif {[regexp {^!(.+)$} $name -> event]} { |
||||
set newcmd [history event $event] |
||||
} elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name -> old new]} { |
||||
set newcmd [history event -1] |
||||
catch {regsub -all -- $old $newcmd $new newcmd} |
||||
} |
||||
if {[info exists newcmd]} { |
||||
tclLog $newcmd |
||||
history change $newcmd 0 |
||||
uplevel 1 [list ::catch $newcmd \ |
||||
::tcl::UnknownResult ::tcl::UnknownOptions] |
||||
dict incr ::tcl::UnknownOptions -level |
||||
return -options $::tcl::UnknownOptions $::tcl::UnknownResult |
||||
} |
||||
|
||||
set ret [catch {set candidates [info commands $name*]} msg] |
||||
if {$name eq "::"} { |
||||
set name "" |
||||
} |
||||
if {$ret != 0} { |
||||
dict append opts -errorinfo \ |
||||
"\n (expanding command prefix \"$name\" in unknown)" |
||||
return -options $opts $msg |
||||
} |
||||
# Filter out bogus matches when $name contained |
||||
# a glob-special char [Bug 946952] |
||||
if {$name eq ""} { |
||||
# Handle empty $name separately due to strangeness |
||||
# in [string first] (See RFE 1243354) |
||||
set cmds $candidates |
||||
} else { |
||||
set cmds [list] |
||||
foreach x $candidates { |
||||
if {[string first $name $x] == 0} { |
||||
lappend cmds $x |
||||
} |
||||
} |
||||
} |
||||
if {[llength $cmds] == 1} { |
||||
uplevel 1 [list ::catch [lreplace $args 0 0 [lindex $cmds 0]] \ |
||||
::tcl::UnknownResult ::tcl::UnknownOptions] |
||||
dict incr ::tcl::UnknownOptions -level |
||||
return -options $::tcl::UnknownOptions $::tcl::UnknownResult |
||||
} |
||||
if {[llength $cmds]} { |
||||
return -code error "ambiguous command name \"$name\": [lsort $cmds]" |
||||
} |
||||
} |
||||
return -code error -errorcode [list TCL LOOKUP COMMAND $name] \ |
||||
"invalid command name \"$name\"" |
||||
} |
||||
|
||||
|
||||
proc know {cond body} { |
||||
proc unknown {args} [string map [list @c@ $cond @b@ $body] { |
||||
if {![catch {expr {@c@}} res] && $res} { |
||||
return [eval {@b@}] |
||||
#tailcall @b@ |
||||
} |
||||
}][info body unknown] |
||||
} |
||||
proc know? {} { |
||||
puts [string range [info body unknown] 0 511] |
||||
} |
||||
if 1 { |
||||
know {[expr $args] || 1} {expr $args} |
||||
know {[regexp {^([0-9]+)\.\.([0-9]+)$} [lindex $args 0] -> from to]} { |
||||
set res {} |
||||
while {$from<=$to} {lappend res $from; incr from} |
||||
set res |
||||
} |
||||
|
||||
|
||||
#run as raw string instead of tcl-list - no variable subst etc |
||||
proc do_runraw {commandline} { |
||||
#return [shellfilter::run [lrange $args 1 end] -teehandle punk -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
||||
puts stdout ">>runraw got: $commandline" |
||||
|
||||
#run always echoes anyway.. as we aren't diverting stdout/stderr off for capturing |
||||
#for consistency with other runxxx commands - we'll just consume it. (review) |
||||
#set wordparts [regexp -inline -all {\S+} $commandline] |
||||
package require string::token::shell |
||||
set parts [string token shell -indices $commandline] |
||||
puts stdout ">>shellparts: $parts" |
||||
|
||||
set runwords [list] |
||||
foreach p $parts { |
||||
set ptype [lindex $p 0] |
||||
set pval [lindex $p 3] |
||||
if {$ptype eq "PLAIN"} { |
||||
lappend runwords [lindex $p 3] |
||||
} elseif {$ptype eq "D:QUOTED"} { |
||||
set v {"} |
||||
append v $pval |
||||
append v {"} |
||||
lappend runwords $v |
||||
} elseif {$ptype eq "S:QUOTED"} { |
||||
set v {'} |
||||
append v $pval |
||||
append v {'} |
||||
lappend runwords $v |
||||
} |
||||
} |
||||
puts stdout ">>runraw runwords: $runwords" |
||||
set runwords [lrange $runwords 1 end] |
||||
|
||||
puts stdout ">>runraw runwords: $runwords" |
||||
#set args [lrange $args 1 end] |
||||
#set runwords [lrange $wordparts 1 end] |
||||
|
||||
set known_runopts [list "-echo" "-e" "-terminal" "-t"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo" "-t" "-terminal" "-terminal" "-terminal"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdwords [list] |
||||
set idx_first_cmdarg [lsearch -not $runwords "-*"] |
||||
set runopts [lrange $runwords 0 $idx_first_cmdarg-1] |
||||
set cmdwords [lrange $runwords $idx_first_cmdarg end] |
||||
|
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "runout: Unknown runoption $o" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
|
||||
set cmd_as_string [join $cmdwords " "] |
||||
puts stdout ">>cmd_as_string: $cmd_as_string" |
||||
|
||||
if {"-terminal" in $runopts} { |
||||
set tcmd [shellfilter::get_scriptrun_from_cmdlist_dquote_if_not $cmdwords] |
||||
puts stdout ">>tcmd: $tcmd" |
||||
#set exitinfo [shellfilter::run $tcmd -teehandle punk -inbuffering line -outbuffering none ] |
||||
set exitinfo "not-implemented" |
||||
} else { |
||||
set exitinfo [shellfilter::run $cmdwords -teehandle punk -inbuffering line -outbuffering none ] |
||||
} |
||||
|
||||
if {[dict exists $exitinfo error]} { |
||||
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||
error [dict get $exitinfo error] |
||||
} |
||||
return $exitinfo |
||||
} |
||||
|
||||
#NOTE: the run,runout,runerr,runx commands only produce an error if the command didn't run. |
||||
# - If it did run, but there was a non-zero exitcode it is up to the application to check that. |
||||
#This is deliberate, but means 'catch' doesn't catch errors within the command itself - the exitcode has to be checked. |
||||
#The user can always use exec for different process error semantics (they don't get exitcode with exec) |
||||
know {[lindex $args 0] eq "runraw"} { |
||||
return [do_run $args] |
||||
} |
||||
know {[lindex $args 0] eq "run"} { |
||||
set args [lrange $args 1 end] |
||||
set known_runopts [list "-echo" "-e"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdargs [list] |
||||
set idx_first_cmdarg [lsearch -not $args "-*"] |
||||
set runopts [lrange $args 0 $idx_first_cmdarg-1] |
||||
set cmdargs [lrange $args $idx_first_cmdarg end] |
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "run: Unknown runoption $o" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
|
||||
set exitinfo [shellfilter::run $cmdargs -teehandle punk -inbuffering none -outbuffering none ] |
||||
} |
||||
|
||||
know {[lindex $args 0] eq "runout"} { |
||||
set ::runout "" |
||||
|
||||
set args [lrange $args 1 end] |
||||
set known_runopts [list "-echo" "-e"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdargs [list] |
||||
set idx_first_cmdarg [lsearch -not $args "-*"] |
||||
set runopts [lrange $args 0 $idx_first_cmdarg-1] |
||||
set cmdargs [lrange $args $idx_first_cmdarg end] |
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "runout: Unknown runoption $o" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
|
||||
#puts stdout "RUNOUT cmdargs: $cmdargs" |
||||
|
||||
#set outvar_stackid [shellfilter::stack::add commandout tee_to_var -action float -settings {-varname ::runout}] |
||||
if {"-echo" in $runopts} { |
||||
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action sink-locked -settings {-varname ::runout}] |
||||
} else { |
||||
set stdout_stackid [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::runout}] |
||||
} |
||||
|
||||
#shellfilter::run [lrange $args 1 end] -teehandle punk -outchan stdout -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler |
||||
set exitinfo [shellfilter::run $cmdargs -teehandle punk -inbuffering none -outbuffering none ] |
||||
|
||||
shellfilter::stack::remove stdout $stdout_stackid |
||||
#shellfilter::stack::remove commandout $outvar_stackid |
||||
if {[dict exists $exitinfo error]} { |
||||
#we must raise an error. |
||||
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||
error [dict get $exitinfo error] |
||||
} |
||||
puts stderr $exitinfo |
||||
return $::runout |
||||
} |
||||
know {[lindex $args 0] eq "runerr"} { |
||||
set ::runerr "" |
||||
|
||||
set args [lrange $args 1 end] |
||||
set known_runopts [list "-echo" "-e"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdargs [list] |
||||
set idx_first_cmdarg [lsearch -not $args "-*"] |
||||
set runopts [lrange $args 0 $idx_first_cmdarg-1] |
||||
set cmdargs [lrange $args $idx_first_cmdarg end] |
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "runout: Unknown runoption $o" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
|
||||
if {"-echo" in $runopts} { |
||||
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action sink-locked -settings {-varname ::runerr}] |
||||
} else { |
||||
set stderr_stackid [shellfilter::stack::add stderr var -action sink-locked -settings {-varname ::runerr}] |
||||
} |
||||
set exitinfo [shellfilter::run $cmdargs -teehandle punk -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
||||
shellfilter::stack::remove stderr $stderr_stackid |
||||
|
||||
|
||||
#we raise an error because an error during calling is different to collecting stderr from a command, and the caller should be able to wrap in a catch |
||||
# to determine something other than just a nonzero exit code or output on stderr. |
||||
if {[dict exists $exitinfo error]} { |
||||
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||
error [dict get $exitinfo error] |
||||
} |
||||
|
||||
|
||||
puts stderr \n$exitinfo |
||||
return $::runerr |
||||
} |
||||
know {[lindex $args 0] eq "runx"} { |
||||
set ::runerr "" |
||||
set ::runout "" |
||||
|
||||
set args [lrange $args 1 end] |
||||
set known_runopts [list "-echo" "-e"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdargs [list] |
||||
set idx_first_cmdarg [lsearch -not $args "-*"] |
||||
set runopts [lrange $args 0 $idx_first_cmdarg-1] |
||||
set cmdargs [lrange $args $idx_first_cmdarg end] |
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "runout: Unknown runoption $o" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
|
||||
|
||||
|
||||
#shellfilter::stack::remove stdout $::repl::id_outstack |
||||
|
||||
if {"-echo" in $runopts} { |
||||
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action sink-locked -settings {-varname ::runerr}] |
||||
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action sink-locked -settings {-varname ::runout}] |
||||
} else { |
||||
set stderr_stackid [shellfilter::stack::add stderr var -action sink-locked -settings {-varname ::runerr}] |
||||
set stdout_stackid [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::runout}] |
||||
} |
||||
|
||||
set exitinfo [shellfilter::run $cmdargs -teehandle punk -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
||||
|
||||
shellfilter::stack::remove stdout $stdout_stackid |
||||
shellfilter::stack::remove stderr $stderr_stackid |
||||
|
||||
set ::repl::output "" |
||||
|
||||
|
||||
#set x [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::repl::runxoutput}] |
||||
set pretty "" |
||||
append pretty "stdout\n" |
||||
if {[string length $::runout]} { |
||||
append pretty "$::runout\n" |
||||
} |
||||
append pretty "stderr\n" |
||||
if {[string length $::runerr]} { |
||||
append pretty "$::runerr\n" |
||||
} |
||||
append pretty "$exitinfo" |
||||
#set ::repl::result_print 0 |
||||
#return [lindex [list [list stdout $::runout stderr $::runerr {*}$exitinfo] [shellfilter::stack::remove stdout $x][puts -nonewline stdout $pretty][set ::repl::output ""]] 0] |
||||
|
||||
set ::repl::result_pretty $pretty |
||||
|
||||
if {[dict exists $exitinfo error]} { |
||||
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||
error [dict get $exitinfo error] |
||||
} |
||||
|
||||
|
||||
return [list stdout $::runout stderr $::runerr {*}$exitinfo] |
||||
|
||||
|
||||
#return [string map [list %o% [list $::runout] %e% [list $::runerr] %x% $exitinfo] {stdout\ |
||||
# %o%\ |
||||
# stderr\ |
||||
# %e%\ |
||||
# %x%\ |
||||
#}] |
||||
} |
||||
} |
||||
namespace eval repl { |
||||
variable output "" |
||||
#important not to initialize - as it can be preset by cooperating package before app-punk has been package required |
||||
variable post_script |
||||
} |
||||
|
||||
|
||||
proc repl::doprompt {prompt} { |
||||
#prompt to stderr. |
||||
#We can pipe commands into repl's stdin without the prompt interfering with the output. |
||||
#Although all command output for each line goes to stdout - not just what is emmited with puts |
||||
if {$::tcl_interactive} { |
||||
puts -nonewline stderr $prompt |
||||
flush stderr |
||||
} |
||||
} |
||||
|
||||
proc repl::start {inchan} { |
||||
variable command |
||||
variable running |
||||
variable reading |
||||
variable done |
||||
set running 1 |
||||
set command "" |
||||
doprompt "P% " |
||||
fileevent $inchan readable [list [namespace current]::repl_handler $inchan] |
||||
set reading 1 |
||||
vwait [namespace current]::done |
||||
#todo - override exit? |
||||
after 0 ::repl::post_operations |
||||
vwait repl::post_operations_done |
||||
return 0 |
||||
} |
||||
proc repl::post_operations {} { |
||||
if {[info exists ::repl::post_script] && [string length $::repl::post_script]} { |
||||
#put aside post_script so the script has the option to add another post_script and restart the repl |
||||
set ::repl::running_script $::repl::post_script |
||||
set ::repl::post_script "" |
||||
uplevel #0 {eval $::repl::running_script} |
||||
} |
||||
|
||||
#todo - tidyup so repl could be restarted |
||||
|
||||
|
||||
set repl::post_operations_done 0 |
||||
} |
||||
|
||||
|
||||
proc repl::reopen_stdin {} { |
||||
if {$::tcl_platform(platform) eq "windows"} { |
||||
puts stderr "|repl> Attempting reconnection of console to stdin by opening 'CON'" |
||||
} else { |
||||
puts stderr "|repl> Attempting reconnection of console to stdin by opening '/dev/tty'" |
||||
} |
||||
#puts stderr "channels:[chan names]" |
||||
#flush stderr |
||||
chan close stdin |
||||
if {$::tcl_platform(platform) eq "windows"} { |
||||
set s [open "CON" r] |
||||
} else { |
||||
#/dev/tty - reference to the controlling terminal for a process |
||||
#review/test |
||||
set s [open "/dev/tty" r] |
||||
} |
||||
|
||||
repl::start stdin |
||||
} |
||||
proc quit {} { |
||||
set ::repl::done "quit" |
||||
} |
||||
#just a failed experiment.. tried various things |
||||
proc repl::reopen_stdinX {} { |
||||
#windows - todo unix |
||||
package require twapi |
||||
|
||||
if 0 { |
||||
if {[catch {package require Memchan} errM]} { |
||||
#package require tcl::chan::fifo2 |
||||
#lassign [tcl::chan::fifo2] a b |
||||
package require tcl::chan::fifo |
||||
set x [tcl::chan::fifo] |
||||
} else { |
||||
#lassign [fifo2] a b |
||||
set x [fifo] |
||||
} |
||||
#first channel opened after stdin closed becomes stdin |
||||
#use a fifo or fifo2 because [chan pipe] assigns the wrong end first! |
||||
#a will be stdin |
||||
} |
||||
#these can't replace proper stdin (filehandle 0) because they're not 'file backed' or 'os level' |
||||
#try opening a named pipe server to become stdin |
||||
set pipename {\\.\pipe\stdin_%id%} |
||||
set pipename [string map [list %id% [pid]] $pipename] |
||||
|
||||
|
||||
|
||||
package require tcl::chan::fifo |
||||
|
||||
chan close stdin |
||||
lassign [tcl::chan::fifo] a |
||||
|
||||
|
||||
puts stderr "newchan: $a" |
||||
puts stderr "|test> $a [chan conf $a]" |
||||
|
||||
#set server [twapi::namedpipe_server $pipename] |
||||
#set client [twapi::namedpipe_client $pipename] ;#open a client and connect to the server we just made |
||||
|
||||
puts stderr "chan names: [chan names]" |
||||
|
||||
#by now $server not valid? |
||||
#set server stdin |
||||
|
||||
#chan configure $server -buffering line -encoding unicode |
||||
#chan configure $client -buffering line -encoding unicode |
||||
|
||||
#puts stderr "|test>ns-server $server [chan conf $server]" |
||||
#puts stderr "|test>ns-client $client [chan conf $client]" |
||||
|
||||
set conin [twapi::get_console_handle stdin] |
||||
twapi::set_standard_handle stdin $conin |
||||
|
||||
set h_in [twapi::get_standard_handle stdin] |
||||
|
||||
puts stderr "|test> $a [chan conf $a]" |
||||
|
||||
#chan configure $client -blocking 0 |
||||
after 10 repl::start $a |
||||
|
||||
} |
||||
proc repl::repl_handler {chan} { |
||||
variable command |
||||
variable running |
||||
variable reading |
||||
variable post_script |
||||
variable id_outstack |
||||
variable result_print |
||||
variable result_pretty |
||||
set chunksize [gets $chan line] |
||||
if {$chunksize < 0} { |
||||
if {[chan eof $chan]} { |
||||
fileevent $chan readable {} |
||||
set reading 0 |
||||
set running 0 |
||||
if {$::tcl_interactive} { |
||||
puts stderr "\n|repl> EOF on $chan." |
||||
} |
||||
set [namespace current]::done 1 |
||||
#test |
||||
repl::reopen_stdin |
||||
return |
||||
} |
||||
} |
||||
append command $line |
||||
if {[info complete $command]} { |
||||
set ::repl::output "" |
||||
set id_outstack [shellfilter::stack::add stdout tee_to_var -settings {-varname ::repl::output}] |
||||
#chan configure stdout -buffering none |
||||
fileevent $chan readable {} |
||||
set reading 0 |
||||
set result_print 1 |
||||
set result_pretty "" |
||||
#don't let unknown use 'args' to convert command to list |
||||
|
||||
if {[string equal -length [string length "runraw "] "runraw " $command]} { |
||||
set status [catch {uplevel #0 [list do_runraw $command]} result] |
||||
} else { |
||||
set status [catch {uplevel #0 $command} result] |
||||
} |
||||
set lastoutchar [string range $::repl::output end-1 end] |
||||
#puts stderr "<output>'$::repl::output' lastoutchar:'$lastoutchar' result:'$result'" |
||||
shellfilter::stack::remove stdout $id_outstack |
||||
if {!$result_print} { |
||||
set result "" |
||||
set lastoutchar "" |
||||
} |
||||
#$command is an unevaluated script at this point |
||||
# so may not be a well formed list e.g 'set x [list a "b"]' |
||||
#- lindex will fail |
||||
#if {[lindex $command 0] eq "runx"} {} |
||||
|
||||
set test [string trim $command] |
||||
if {[string equal -length [string length "runx "] "runx " $command]} { |
||||
if {[string length $result_pretty]} { |
||||
set result $result_pretty |
||||
} |
||||
} |
||||
fileevent $chan readable [list [namespace current]::repl_handler $chan] |
||||
set reading 1 |
||||
if {$result ne ""} { |
||||
if {$status == 0} { |
||||
if {[string length $lastoutchar]} { |
||||
puts \n$result |
||||
} else { |
||||
puts $result |
||||
} |
||||
doprompt "P% " |
||||
} else { |
||||
puts stderr $result |
||||
doprompt "p% " |
||||
} |
||||
} else { |
||||
if {[string length $lastoutchar]} { |
||||
doprompt "\nP% " |
||||
} else { |
||||
doprompt "P% " |
||||
} |
||||
} |
||||
set command "" |
||||
} else { |
||||
append command \n |
||||
doprompt "> " |
||||
} |
||||
} |
||||
repl::start stdin |
||||
|
||||
exit 0 |
||||
|
||||
#repl::start $program_read_stdin_pipe |
||||
|
@ -0,0 +1,3 @@
|
||||
|
||||
package ifneeded app-shellspy 1.0 [list source [file join $dir shellspy.tcl]] |
||||
|
@ -0,0 +1,717 @@
|
||||
#! /usr/bin/env tclsh |
||||
# |
||||
#copyright 2023 Julian Marcel Noble |
||||
#license: BSD (revised 3-clause) |
||||
# |
||||
#see notes at beginning of shellspy namespace re stdout/stderr |
||||
# |
||||
#SHELLSPY - provides commandline flag information and stdout/stderr logging/mirroring without output/pipeline interference, |
||||
# or modified output if modifying filters explicitly configured. |
||||
# |
||||
#shellspy uses the shellfilter and flagfilter libraries to run a commandline with tee-diversions of stdout/stderr to configured syslog/file logs |
||||
#Because it is a tee, the command's stdout/stderr are still available as direct output from this script. |
||||
#Various filters/modifiers/redirections can be placed on the channel stacks for stdin/stderr using the shellfilter::stack api |
||||
# and other shellfilter:: helpers such as shellfilter::redir_output_to_log |
||||
# Redirecting stderr/stdout in this way prior to the actual command run will not interfere with the command/pipeline output due to the way |
||||
# shellfilter temporarily inserts it's own tee into the stack at the point of the highest existing redirection it encounters. |
||||
# |
||||
#A note on input/output convention regarding channels/pipes |
||||
# we write to an output, read from an input. |
||||
# e.g when creating a pipe with 'lassign [chan pipe] in out' we write to out and read from in. |
||||
# This is potentially confusing from an OO-like perspective thinking of a pipe object as a sort of object. |
||||
# Don't think of it from the perspective of the pipe - but from the program using it. |
||||
# This is not a convention invented here for shellspy - but it seems to match documentation and common use e.g for 'chan pending input|output chanid' |
||||
# This matches the way we write to stdout read from stdin. |
||||
# Possibly using read/write related naming for pipe ends is clearer. eg 'lassign [chan pipe] rd_pipe1 wr_pipe1' |
||||
# |
||||
package provide app-shellspy 1.0 |
||||
|
||||
|
||||
#a test for windows |
||||
#fconfigure stdin -encoding utf-16le |
||||
#fconfigure stdout -encoding utf-16le |
||||
|
||||
#tcl::tm::remove {*}[tcl::tm::list] |
||||
|
||||
#add dir outside of tclkit/exe so we can override with higher versions if necessary without rebuilding |
||||
set m_dir [file normalize [file join [file dirname [info nameofexecutable]] modules]] |
||||
tcl::tm::add $m_dir |
||||
set m_dir [file normalize [file join [file dirname [info script]] ../../../modules]] |
||||
tcl::tm::add $m_dir |
||||
|
||||
|
||||
#experiment - todo make a flag for it if it's useful |
||||
#Middle cap for direct dispatch without flagcheck arg processing or redirections or REPL. |
||||
set arg1 [lindex $::argv 0] |
||||
if {[file extension $arg1] in [list .tCl]} { |
||||
set ::argv [lrange $::argv 1 end] |
||||
set ::argc [llength $::argv] |
||||
set scriptfile [file normalize $arg1] |
||||
if {![file exists $scriptfile]} { |
||||
#try the lowercase version (extension lowercased only) so that file doesn't have to be renamed to use alternate dispatch |
||||
set scriptfile [file rootname $scriptfile][string tolower [file extension $scriptfile]] |
||||
} |
||||
source [file normalize $arg1] |
||||
|
||||
#package require app-punk |
||||
|
||||
} else { |
||||
|
||||
|
||||
|
||||
#set m_dir [file join $starkit::topdir modules] |
||||
|
||||
#lappend auto_path c:/tcl/lib/tcllib1.20 |
||||
package require flagfilter |
||||
package require shellfilter |
||||
package require Thread |
||||
|
||||
#package require packageTrace |
||||
|
||||
set ::testconfig 5 |
||||
|
||||
namespace eval shellspy { |
||||
variable shellspy_status_log "shellspy-[clock micros]" |
||||
#shellfilter::log::open $shellspy_status_log [list -tag $shellspy_status_log -syslog 172.16.6.42:51500 -file ""] |
||||
shellfilter::log::open $shellspy_status_log [list -tag $shellspy_status_log -syslog 127.0.0.1:514 -file ""] |
||||
shellfilter::log::write $shellspy_status_log "shellspy launch with args '$::argv'" |
||||
shellfilter::log::write $shellspy_status_log "stdout/stderr encoding: [chan configure stdout -encoding]/[chan configure stderr -encoding]" |
||||
|
||||
#------------------------------------------------------------------------- |
||||
##don't write to stdout/stderr before you've redirected them to a log using shellfilter functions |
||||
## puts to stdout/stderr will comingle with command's output if performed before the channel stacks are configured. |
||||
|
||||
chan configure stdin -buffering line |
||||
chan configure stdout -buffering none |
||||
chan configure stderr -buffering none |
||||
|
||||
#set id_ansistrip [shellfilter::stack::add stderr ansistrip -settings {}] |
||||
#set id_ansistrip [shellfilter::stack::add stdout ansistrip -settings {}] |
||||
|
||||
lassign [shellfilter::redir_output_to_log "SPY"] id_stdout_redir id_stderr_redir |
||||
|
||||
|
||||
### |
||||
#we can now safely write to stderr/stdout and it will not interfere with stderr/stdout from the dispatched command. |
||||
#This is because when shellfilter::run is called it temporarily adds it's own filter in place of the redirection we just added. |
||||
# shellfilter::run installs tee_to_pipe on stdout & stderr with -action sink-aside. |
||||
# sink-aside means to sink down the filter stack to the topmost filter that is diversionary, and replace it. |
||||
# when the command finishes running, the redirecting filter that it displaced is reinstalled in the stack. |
||||
### |
||||
|
||||
### |
||||
#Note that futher filters installed here will sit 'above' any of the redirecting filters |
||||
# so apply to both the shellfilter::run commandline, |
||||
# as well as writes to stderr/stdout from here or other libraries operating in this process. |
||||
# To bypass the the filter-stack and still emit to syslog etc - |
||||
# you can use shellfilter::log::open and shellfilter::log::write e.g |
||||
# shellfilter::log::open "mystatuslog" [list -tag "mystatuslog" -syslog 172.16.6.42:51500 -file ""] |
||||
# shellfilter::log::write "mystatuslog" "shellspy launch" |
||||
# |
||||
#### |
||||
#set id_ansistrip [shellfilter::stack::add stderr ansistrip -action float -settings {}] |
||||
#set id_ansistrip [shellfilter::stack::add stdout ansistrip -action float -settings {}] |
||||
|
||||
|
||||
##stdin stack operates in reverse compared to stdout/stderr in that first added to stack is first to see the data |
||||
##for stdin: first added to stack is 'upstream' as it will always encounter the data before later ones added to the stack. |
||||
##for stdout: first added to stack is 'downstream' as it will alays encounter the data after others on the stack |
||||
#shellfilter::stack::add stdin ansistrip -action {} -settings {} |
||||
#shellfilter::stack::add stdin tee_to_log -settings {-tag "input" -raw 1 -syslog 172.16.6.42:51500 -file ""} |
||||
|
||||
#------------------------------------------------------------------------- |
||||
##note - to use shellfilter::stack::new - the fifo2 end at the local side requires the event loop to be running |
||||
## for interactive testing a relatively simple repl.tcl can be used. |
||||
|
||||
#todo - default to no logging.. add special flag in checkflags to indicate end of options for matched command only e.g --- ? |
||||
# then prioritize these results from checkflags to configure pre-dispatch behaviour by running a predispatch script(?) |
||||
# |
||||
# we can log flag-processing info coming from checkflags.. but we don't want to do two opt scans and emit it twice. |
||||
# configuration of the logging for flag/opt parsing should come from a config file and default to none. |
||||
set stdout_log [file normalize ~]/shellspy-stdout.txt |
||||
set stderr_log [file normalize ~]/shellspy-stderr.txt |
||||
|
||||
set outdeviceinfo [shellfilter::stack::new shellspyout -settings [list -tag "shellspyout" -buffering none -raw 1 -syslog 127.0.0.1:514 -file $stdout_log]] |
||||
set commandlog [dict get $outdeviceinfo localchan] |
||||
#puts $commandlog "HELLO $commandlog" |
||||
#flush $commandlog |
||||
set errdeviceinfo [shellfilter::stack::new shellspyerr -settings [list -tag "shellspyerr" -buffering none -raw 1 -syslog 127.0.0.1:514 -file $stderr_log]] |
||||
|
||||
#note that this filter is inline with the data teed off to the shellspyout log. |
||||
#To filter the main stdout an addition to the stdout stack can be made. specify -action float to have it affect both stdout and the tee'd off data. |
||||
set id_ansistrip [shellfilter::stack::add shellspyout ansistrip -settings {}] |
||||
|
||||
|
||||
#set id_out [shellfilter::stack::add stdout rebuffer -settings {}] |
||||
|
||||
#an example filter to capture some output to a var for later use - this one is for ansible-playbook |
||||
#set ::recap "" |
||||
#set id_tee_grep [shellfilter::stack::add shellspyout tee_grep_to_var -settings {-grep ".*PLAY RECAP.*|.*fatal:.*|.*changed:.*" -varname ::recap -pre 1 -post 3}] |
||||
|
||||
namespace import ::flagfilter::check_flags |
||||
|
||||
namespace eval shellspy::callbacks {} |
||||
namespace eval shellspy::parameters {} |
||||
|
||||
|
||||
proc do_callback {func args} { |
||||
variable shellspy_status_log |
||||
set exedir [file dirname [info nameofexecutable]] |
||||
set dispatchtcl [file join $exedir callbacks dispatch.tcl] |
||||
if {[file exists $dispatchtcl]} { |
||||
source $dispatchtcl |
||||
if {[llength [info commands shellspy::callbacks::$func]]} { |
||||
shellfilter::log::write $shellspy_status_log "found shellspy::callbacks::$func - calling" |
||||
if {[catch { |
||||
set args [shellspy::callbacks::$func {*}$args] |
||||
} errmsg]} { |
||||
shellfilter::log::write $shellspy_status_log "ERROR in shellspy::callbacks::$func\n errormsg:$errmsg" |
||||
error $errmsg |
||||
} |
||||
} |
||||
} |
||||
return $args |
||||
} |
||||
proc do_callback_parameters {func args} { |
||||
variable shellspy_status_log |
||||
set exedir [file dirname [info nameofexecutable]] |
||||
set paramtcl [file join $exedir callbacks parameters.tcl] |
||||
set params $args |
||||
if {[file exists $paramtcl]} { |
||||
source $paramtcl |
||||
if {[llength [info commands shellspy::parameters::$func]]} { |
||||
shellfilter::log::write $shellspy_status_log "found shellspy::parameters::$func - calling" |
||||
if {[catch { |
||||
set params [shellspy::parameters::$func $params] |
||||
} errmsg]} { |
||||
shellfilter::log::write $shellspy_status_log "ERROR in shellspy::parameters::$func\n errormsg:$errmsg" |
||||
} |
||||
} |
||||
} |
||||
return $params |
||||
} |
||||
|
||||
#some tested configs |
||||
proc get_channel_config {config} { |
||||
#note tcl script being called from wrong place.. configs don't affect: todo - move it. |
||||
set params [dict create] |
||||
if {$config == 0} { |
||||
#bad for: everything. extra cr |
||||
dict set params -inbuffering line |
||||
dict set params -outbuffering line |
||||
dict set params -readprocesstranslation auto ;#default |
||||
dict set params -outtranslation auto |
||||
} |
||||
|
||||
if {$config == 1} { |
||||
#ok for: cmd, cmd/u/c,raw,pwsh, sh,raw, tcl script process |
||||
#not ok for: bash,wsl, tcl script |
||||
dict set params -inbuffering line |
||||
dict set params -outbuffering line |
||||
dict set params -readprocesstranslation auto ;#default |
||||
dict set params -outtranslation lf |
||||
} |
||||
if {$config == 2} { |
||||
#ok for: cmd, cmd/uc,pwsh,sh , tcl script process |
||||
#not ok for: tcl script, bash, wsl |
||||
dict set params -inbuffering none ;#default |
||||
dict set params -outbuffering none ;#default |
||||
dict set params -readprocesstranslation auto ;#default |
||||
dict set params -outtranslation lf ;#default |
||||
} |
||||
if {$config == 3} { |
||||
#ok for: cmd |
||||
dict set params -inbuffering line |
||||
dict set params -outbuffering line |
||||
dict set params -readprocesstranslation lf |
||||
dict set params -outtranslation lf |
||||
} |
||||
if {$config == 4} { |
||||
#ok for: cmd,cmd/uc,raw,sh |
||||
#not ok for pwsh,bash,wsl, tcl script, tcl script process |
||||
dict set params -inbuffering none |
||||
dict set params -outbuffering none |
||||
dict set params -readprocesstranslation lf |
||||
dict set params -outtranslation lf |
||||
} |
||||
|
||||
if {$config == 5} { |
||||
#ok for: pwsh,cmd,cmd/u/c,raw,sh, tcl script process |
||||
#not ok for bash,wsl |
||||
#ok for vim cmd/u/c but only with to_unix filter on stdout (works in gvim and console) |
||||
dict set params -inbuffering none |
||||
dict set params -outbuffering none |
||||
dict set params -readprocesstranslation crlf |
||||
dict set params -outtranslation lf |
||||
} |
||||
if {$config == 6} { |
||||
#ok for: cmd,cmd/u/c,pwsh,raw,sh,bash |
||||
#not ok for: vim with cmd /u/c (?) |
||||
dict set params -inbuffering line |
||||
dict set params -outbuffering line |
||||
dict set params -readprocesstranslation crlf |
||||
dict set params -outtranslation lf |
||||
} |
||||
if {$config == 7} { |
||||
#ok for: sh,bash |
||||
#not ok for: wsl (display ok but extra cr), cmd,cmd/u/c,pwsh, tcl script, tcl script process, raw |
||||
dict set params -inbuffering none |
||||
dict set params -outbuffering none |
||||
dict set params -readprocesstranslation crlf |
||||
dict set params -outtranslation crlf |
||||
} |
||||
if {$config == 8} { |
||||
#not ok for anything..all have extra cr |
||||
dict set params -inbuffering none |
||||
dict set params -outbuffering none |
||||
dict set params -readprocesstranslation lf |
||||
dict set params -outtranslation crlf |
||||
} |
||||
return $params |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
proc do_in_powershell {args} { |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_in_powershell got '$args'" |
||||
set args [do_callback powershell {*}$args] |
||||
set params [do_callback_parameters powershell] |
||||
dict set params -teehandle shellspy |
||||
|
||||
|
||||
#readprocesstranslation lf - doesn't work for buffering line or none |
||||
#readprocesstranslation crlf works for buffering line and none with outchantranslation lf |
||||
|
||||
set params [dict merge $params [get_channel_config $::testconfig]] ;#working: 5 unbuffered, 6 linebuffered |
||||
|
||||
|
||||
|
||||
#set exitinfo [shellfilter::run [list pwsh -nologo -noprofile -c {*}$args] -debug 1] |
||||
|
||||
|
||||
set exitinfo [shellfilter::run [list pwsh -nologo -noprofile -c {*}$args] {*}$params] |
||||
#Passing args in as a single element will tend to make powershell treat the args as a 'script block' |
||||
# (powershell sees items in curly brackets {} as a scriptblock - when called from non-powershell, this tends to just echo back the contents) |
||||
#set exitinfo [shellfilter::run [list pwsh -nologo -noprofile -c $args] {*}$params] |
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
shellfilter::log::write $shellspy_status_log "do_in_powershell returning $exitinfo" |
||||
#exit [lindex $exitinfo 1] |
||||
} |
||||
} |
||||
proc do_in_powershell_terminal {args} { |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_in_powershell_terminal got '$args'" |
||||
dict set params -teehandle shellspy |
||||
set params [dict merge $params [get_channel_config $::testconfig]] ;#working: 5 unbuffered, 6 linebuffered |
||||
|
||||
set cmdlist [list pwsh -nologo -noprofile -c {*}$args] |
||||
set cmdlist [shellfilter::get_scriptrun $cmdlist] |
||||
shellfilter::log::write $shellspy_status_log "do_in_powershell_terminal as script: '$cmdlist'" |
||||
|
||||
set exitinfo [shellfilter::run $cmdlist {*}$params] |
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
shellfilter::log::write $shellspy_status_log "do_in_powershell_terminal returning $exitinfo" |
||||
} |
||||
} |
||||
|
||||
|
||||
proc do_in_cmdshell {args} { |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_in_cmdshell got '$args'" |
||||
set args [do_callback cmdshell {*}$args] |
||||
set params [do_callback_parameters cmdshell] |
||||
|
||||
|
||||
dict set params -teehandle shellspy |
||||
dict set params -copytempfile 1 |
||||
|
||||
set params [dict merge $params [get_channel_config $::testconfig]] |
||||
|
||||
#set id_out [shellfilter::stack::add stdout tounix -action sink-locked -settings {}] |
||||
|
||||
#set exitinfo [shellfilter::run "cmd /c $args" -debug 1] |
||||
set exitinfo [shellfilter::run [list cmd /c {*}$args] {*}$params] |
||||
#set exitinfo [shellfilter::run [list cmd /c {*}$args] -debug 1] |
||||
|
||||
#shellfilter::stack::remove stdout $id_out |
||||
|
||||
|
||||
|
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
#exit [lindex $exitinfo 1] |
||||
shellfilter::log::write $shellspy_status_log "do_in_cmdshell returning $exitinfo" |
||||
#puts stderr "do_in_cmdshell returning $exitinfo" |
||||
} |
||||
} |
||||
proc do_in_cmdshellb {args} { |
||||
|
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_in_cmdshellb got '$args'" |
||||
|
||||
set args [do_callback cmdshellb {*}$args] |
||||
|
||||
|
||||
shellfilter::log::write $shellspy_status_log "do_in_cmdshellb post_callback args '$args'" |
||||
|
||||
set params [do_callback_parameters cmdshellb] |
||||
dict set params -teehandle shellspy |
||||
dict set params -copytempfile 1 |
||||
dict set params -debug 0 |
||||
|
||||
#----------------------------- |
||||
#channel config 6 and towindows sink-aside-locked {-junction 1} works with vim-flog |
||||
#----------------------------- |
||||
set params [dict merge $params [get_channel_config 6]] |
||||
#set id_out [shellfilter::stack::add stdout tounix -action sink-aside-locked -settings {-junction 1}] |
||||
|
||||
|
||||
set exitinfo [shellfilter::run [list cmd /u/c {*}$args] {*}$params] |
||||
|
||||
#shellfilter::stack::remove stdout $id_out |
||||
|
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
shellfilter::log::write $shellspy_status_log "do_in_cmdshellb returning with exitcode $exitinfo" |
||||
} else { |
||||
shellfilter::log::write $shellspy_status_log "do_in_cmdshellb returning WITHOUT exitcode $exitinfo" |
||||
} |
||||
} |
||||
proc do_in_cmdshelluc {args} { |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_in_cmdshelluc got '$args'" |
||||
set args [do_callback cmdshelluc {*}$args] |
||||
set params [do_callback_parameters cmdshell] |
||||
#set exitinfo [shellfilter::run "cmd /c $args" -debug 1] |
||||
dict set params -teehandle shellspy |
||||
dict set params -copytempfile 1 |
||||
dict set params -debug 0 |
||||
|
||||
#set params [dict merge $params [get_channel_config $::testconfig]] |
||||
|
||||
set params [dict merge $params [get_channel_config 1]] |
||||
#set id_out [shellfilter::stack::add stdout tounix -action sink-locked -settings {}] |
||||
|
||||
set id_out [shellfilter::stack::add stdout tounix -action sink-locked -settings {}] |
||||
|
||||
set exitinfo [shellfilter::run [list cmd /u/c {*}$args] {*}$params] |
||||
shellfilter::stack::remove stdout $id_out |
||||
#chan configure stdout -translation crlf |
||||
|
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
#exit [lindex $exitinfo 1] |
||||
shellfilter::log::write $shellspy_status_log "do_in_cmdshelluc returning $exitinfo" |
||||
#puts stderr "do_in_cmdshell returning $exitinfo" |
||||
} |
||||
} |
||||
proc do_raw {args} { |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_raw got '$args'" |
||||
set args [do_callback raw {*}$args] |
||||
set params [do_callback_parameters raw] |
||||
#set params {} |
||||
dict set params -debug 0 |
||||
#dict set params -outprefix "_test_" |
||||
dict set params -teehandle shellspy |
||||
|
||||
|
||||
set params [dict merge $params [get_channel_config $::testconfig]] |
||||
|
||||
|
||||
if {[llength $params]} { |
||||
set exitinfo [shellfilter::run $args {*}$params] |
||||
} else { |
||||
set exitinfo [shellfilter::run $args -debug 1 -outprefix "j"] |
||||
} |
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
shellfilter::log::write $shellspy_status_log "do_raw returning $exitinfo" |
||||
} |
||||
} |
||||
|
||||
proc do_script_process {scriptname args} { |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_script_process got scriptname:'$scriptname' args:'$args'" |
||||
set args [do_callback script_process {*}$args] |
||||
set params [do_callback_parameters script_process] |
||||
dict set params -teehandle shellspy |
||||
|
||||
set params [dict merge $params [get_channel_config $::testconfig]] |
||||
|
||||
#todo - use glob to check capitalisation of file tail (.TCL vs .tcl .Tcl etc) |
||||
set exitinfo [shellfilter::run [concat [auto_execok tclsh] $scriptname $args] {*}$params] |
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
shellfilter::log::write $shellspy_status_log "do_script_process returning $exitinfo" |
||||
} |
||||
} |
||||
proc do_script {scriptname replwhen args} { |
||||
#ideally we don't want to launch an external process to run the script |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_script got scriptname:'$scriptname' replwhen:$replwhen args:'$args'" |
||||
|
||||
set script [string map [list %a% $args %s% $scriptname] { |
||||
set scriptname %s% |
||||
set ::argv [list %a%] |
||||
set ::argc [llength $::argv] |
||||
source [file normalize $scriptname] |
||||
|
||||
}] |
||||
|
||||
set repl_line "package require app-punk\n" |
||||
|
||||
if {$replwhen eq "repl_first"} { |
||||
#we need to cooperate with the repl to get the script to run on exit |
||||
namespace eval ::repl {} |
||||
set ::repl::post_script $script |
||||
set script "$repl_line" |
||||
} elseif {$replwhen eq "repl_last"} { |
||||
append script $repl_line |
||||
} else { |
||||
#just the script |
||||
} |
||||
|
||||
|
||||
set args [do_callback script {*}$args] |
||||
set params [do_callback_parameters script] |
||||
dict set params -tclscript 1 ;#don't give callback a chance to omit/break this |
||||
dict set params -teehandle shellspy |
||||
|
||||
set params [dict merge $params [get_channel_config $::testconfig]] |
||||
|
||||
|
||||
set exitinfo [shellfilter::run $script {*}$params] |
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
shellfilter::log::write $shellspy_status_log "do_script returning $exitinfo" |
||||
} |
||||
} |
||||
|
||||
proc shellescape {arglist} { |
||||
set out [list] |
||||
foreach a $arglist { |
||||
set a [string map [list \\ \\\\ ] $a] |
||||
lappend out $a |
||||
} |
||||
return $out |
||||
} |
||||
proc do_shell {shell args} { |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_shell $shell got '$args' [llength $args]" |
||||
set args [do_callback $shell {*}$args] |
||||
shellfilter::log::write $shellspy_status_log "do_shell $shell xgot '$args'" |
||||
set params [do_callback_parameters $shell] |
||||
dict set params -teehandle shellspy |
||||
|
||||
|
||||
set params [dict merge $params [get_channel_config $::testconfig]] |
||||
|
||||
set id_out [shellfilter::stack::add stdout towindows -action sink-aside-locked -settings {-junction 1}] |
||||
|
||||
#shells that take -c and need all args passed together as a string |
||||
|
||||
set exitinfo [shellfilter::run [concat $shell -c [shellescape $args]] {*}$params] |
||||
|
||||
shellfilter::stack::remove stdout $id_out |
||||
|
||||
|
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
shellfilter::log::write $shellspy_status_log "do_shell $shell returning $exitinfo" |
||||
} |
||||
} |
||||
proc do_wsl {dist args} { |
||||
variable shellspy_status_log |
||||
shellfilter::log::write $shellspy_status_log "do_wsl $dist got '$args' [llength $args]" |
||||
set args [do_callback wsl {*}$args] ;#use dist? |
||||
shellfilter::log::write $shellspy_status_log "do_wsl $dist xgot '$args'" |
||||
set params [do_callback_parameters wsl] |
||||
|
||||
dict set params -debug 0 |
||||
|
||||
|
||||
set params [dict merge $params [get_channel_config $::testconfig]] |
||||
|
||||
|
||||
set id_out [shellfilter::stack::add stdout towindows -action sink-aside-locked -settings {-junction 1}] |
||||
|
||||
|
||||
dict set params -teehandle shellspy ;#shellspyout shellspyerr must exist |
||||
set exitinfo [shellfilter::run [concat wsl -d $dist -e [shellescape $args]] {*}$params] |
||||
|
||||
|
||||
shellfilter::stack::remove stdout $id_out |
||||
|
||||
|
||||
if {[lindex $exitinfo 0] eq "exitcode"} { |
||||
shellfilter::log::write $shellspy_status_log "do_wsl $dist returning $exitinfo" |
||||
} |
||||
} |
||||
|
||||
#todo - load these from a callback |
||||
set commands [list] |
||||
|
||||
#shout extension to force use of tclsh as a separate process |
||||
#todo - detect various extensions - and use all script-preceding unmatched args as interpreter+options |
||||
#e.g perl,php,python etc. |
||||
#For tcl will make it easy to test different interpreter output from tclkitsh, tclsh8.6, tclsh8.7 etc |
||||
#for now we have a hardcoded default interpreter for tcl as 'tclsh' - todo: get default interps from config |
||||
#(or just attempt launch in case there is shebang line in script) |
||||
#we may get ambiguity with existing shell match-specs such as -c /c -r. todo - only match those in first slot? |
||||
lappend commands [list tclscriptprocess [list match [list .*\.TCL$ .*\.TM$ .*\.TK$] dispatch [list shellspy::do_script_process %matched%] dispatchtype tcl dispatchglobal 1 singleopts {any}]] |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list tclscriptprocess [list sub word$i singleopts {any}]] |
||||
} |
||||
|
||||
#camelcase convention .Tcl script before repl |
||||
lappend commands [list tclscriptbeforerepl [list match [list .*\.Tcl$ .*\.Tm$ .*\.Tk$ ] dispatch [list shellspy::do_script %matched% "repl_last"] dispatchtype tcl dispatchglobal 1 singleopts {any}]] |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list tclscriptbeforerepl [list sub word$i singleopts {any}]] |
||||
} |
||||
|
||||
|
||||
#Backwards Camelcase convention .tcL - means repl first, script last |
||||
lappend commands [list tclscriptafterrepl [list match [list .*\.tcL$ .*\.tM$ .*\.tK$ ] dispatch [list shellspy::do_script %matched% "repl_first"] dispatchtype tcl dispatchglobal 1 singleopts {any}]] |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list tclscriptafterrepl [list sub word$i singleopts {any}]] |
||||
} |
||||
|
||||
|
||||
#we've already handled .Tcl .tcL, .tCl, .TCL - handle any other capitalisations as a script in this process |
||||
lappend commands [list tclscript [list match [list .*\.tcl$ .*\.tCL$ .*\.TCl$ .*\.tm$ .*\.tk$ ] dispatch [list shellspy::do_script %matched% "no_repl"] dispatchtype tcl dispatchglobal 1 singleopts {any}]] |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list tclscript [list sub word$i singleopts {any}]] |
||||
} |
||||
|
||||
|
||||
|
||||
lappend commands [list bashraw [list match ^bash$ dispatch [list shellspy::do_shell bash] dispatchtype raw dispatchglobal 1 singleopts {any}]] |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list bashraw [list sub word$i singleopts {any}]] |
||||
} |
||||
lappend commands {shraw {match ^sh$ dispatch {shellspy::do_shell sh} dispatchtype raw dispatchglobal 1 singleopts {any}}} |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list shraw [list sub word$i singleopts {any}]] |
||||
} |
||||
|
||||
lappend commands [list runbash [list match ^b$ dispatch [list shellspy::do_shell bash] dispatchtype shell dispatchglobal 1 singleopts {any}]] |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list runbash [list sub word$i singleopts {any}]] |
||||
} |
||||
lappend commands {runsh {match ^s$ dispatch {shellspy::do_shell sh} dispatchtype shell dispatchglobal 1 singleopts {any}}} |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list runsh [list sub word$i singleopts {any}]] |
||||
} |
||||
|
||||
lappend commands {runraw {match ^-r$ dispatch shellspy::do_raw dispatchtype raw dispatchglobal 1 singleopts {any}}} |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list runraw [list sub word$i singleopts {any}]] |
||||
} |
||||
lappend commands {runpwsh {match ^-c$ dispatch shellspy::do_in_powershell dispatchtype raw dispatchglobal 1 singleopts {any}}} |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list runpwsh [list sub word$i singleopts {any}]] |
||||
} |
||||
lappend commands {runpwsht {match ^pwsh$ dispatch shellspy::do_in_powershell_terminal dispatchtype raw dispatchglobal 1 singleopts {any}}} |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list runpwsht [list sub word$i singleopts {any}]] |
||||
} |
||||
|
||||
|
||||
lappend commands {runcmd {match ^/c$ dispatch shellspy::do_in_cmdshell dispatchtype raw dispatchglobal 1 singleopts {any} longopts {any}}} |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list runcmd [list sub word$i singleopts {any}]] |
||||
} |
||||
lappend commands {runcmduc {match ^/u/c$ dispatch shellspy::do_in_cmdshelluc dispatchtype raw dispatchglobal 1 singleopts {any} longopts {any}}} |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list runcmduc [list sub word$i singleopts {any}]] |
||||
} |
||||
#cmd with bracked args () e.g with vim shellxquote set to "(" |
||||
lappend commands [list runcmdb [list match ^cmdb$ dispatch [list shellspy::do_in_cmdshellb %matched%] dispatchtype raw dispatchglobal 1 singleopts {any} longopts {any} pairopts {any}]] |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list runcmdb [list sub word$i singleopts {any} longopts {any} pairopts {any}]] |
||||
} |
||||
|
||||
lappend commands [list wslraw [list match ^wsl$ dispatch [list shellspy::do_wsl AlmaLinux9] dispatchtype raw dispatchglobal 1 singleopts {any} longopts {any}]] |
||||
for {set i 0} {$i < 25} {incr i} { |
||||
lappend commands [list wslraw [list sub word$i singleopts {any}]] |
||||
} |
||||
|
||||
|
||||
############################################################################################ |
||||
|
||||
#echo raw args to diverted stderr before running the argument analysis |
||||
puts -nonewline stderr "rawargs: $::argv\n" |
||||
set i 1 |
||||
foreach a $::argv { |
||||
puts -nonewline stderr "arg$i: '$a'\n" |
||||
incr i |
||||
} |
||||
|
||||
|
||||
puts stderr $::shellfilter::ansi::test |
||||
flush stderr |
||||
|
||||
set argdefinitions [list \ |
||||
-caller shellspy_dispatcher \ |
||||
-debugargs 0 \ |
||||
-debugargsonerror 2 \ |
||||
-return all \ |
||||
-soloflags {} \ |
||||
-defaults [list] \ |
||||
-required {none} \ |
||||
-extras {all} \ |
||||
-commandprocessors $commands \ |
||||
-values $::argv ] |
||||
|
||||
|
||||
|
||||
if {[catch { |
||||
set arglist [check_flags {*}$argdefinitions] |
||||
} errMsg]} { |
||||
puts -nonewline stderr "|shellspy-stderr> ERROR during command dispatch\n" |
||||
puts -nonewline stderr "|shellspy-stderr> $errMsg\n" |
||||
puts -nonewline stderr "|shellspy-stderr> [set ::errorInfo]\n" |
||||
|
||||
} else { |
||||
puts stdout "shellspy final-arglist $arglist" |
||||
} |
||||
|
||||
shellfilter::log::write $shellspy_status_log "check_flags dispatch -done-" |
||||
|
||||
#puts stdout "sp2. $::argv" |
||||
|
||||
if {[catch { |
||||
set tidyinfo [shellfilter::logtidyup] |
||||
} errMsg]} { |
||||
|
||||
shellfilter::log::open shellspy-error {-tag shellspy-error -syslog 127.0.0.1:514} |
||||
shellfilter::log::write shellspy-error "logtidyup error $errMsg\n [set ::errorInfo]" |
||||
after 500 |
||||
} |
||||
#don't open more logs.. |
||||
#puts stdout ">$tidyinfo" |
||||
|
||||
|
||||
#shellfilter::log::write $shellspy_status_log "logtidyup -done- $tidyinfo" |
||||
|
||||
|
||||
|
||||
set errorlist [dict get $tidyinfo errors] |
||||
if {[llength $errorlist]} { |
||||
foreach err $errorlist { |
||||
puts -nonewline stderr "|shellspy-final> worker-error-set $err\n" |
||||
} |
||||
} |
||||
puts stdout "shellspy -done-" |
||||
#shellfilter::log::write $shellspy_status_log "shellspy -done-" |
||||
flush stdout |
||||
|
||||
if {[catch { |
||||
shellfilter::logtidyup $shellspy_status_log |
||||
} errMsg]} { |
||||
shellfilter::log::open shellspy-final {-tag shellspy-final -syslog 127.0.0.1:514} |
||||
shellfilter::log::write shellspy-final "FINAL logtidyup error $errMsg\n [set ::errorInfo]" |
||||
after 500 |
||||
|
||||
} |
||||
|
||||
exit 0 |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@
|
||||
Copyright (c) 2003-2012, Ashok P. Nadkarni |
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
- Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- The name of the copyright holder and any other contributors may not |
||||
be used to endorse or promote products derived from this software |
||||
without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,28 @@
|
||||
# |
||||
# Copyright (c) 2010-2012, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# ADSI routines |
||||
|
||||
# TBD - document |
||||
proc twapi::adsi_translate_name {name to {from 0}} { |
||||
set map { |
||||
unknown 0 fqdn 1 samcompatible 2 display 3 uniqueid 6 |
||||
canonical 7 userprincipal 8 canonicalex 9 serviceprincipal 10 |
||||
dnsdomain 12 |
||||
} |
||||
if {! [string is integer -strict $to]} { |
||||
set to [dict get $map $to] |
||||
if {$to == 0} { |
||||
error "'unknown' is not a valid target format." |
||||
} |
||||
} |
||||
|
||||
if {! [string is integer -strict $from]} { |
||||
set from [dict get $map $from] |
||||
} |
||||
|
||||
return [TranslateName $name $from $to] |
||||
} |
@ -0,0 +1,114 @@
|
||||
# |
||||
# Copyright (c) 2003-2012, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi {} |
||||
|
||||
# Get the command line |
||||
proc twapi::get_command_line {} { |
||||
return [GetCommandLineW] |
||||
} |
||||
|
||||
# Parse the command line |
||||
proc twapi::get_command_line_args {cmdline} { |
||||
# Special check for empty line. CommandLinetoArgv returns process |
||||
# exe name in this case. |
||||
if {[string length $cmdline] == 0} { |
||||
return [list ] |
||||
} |
||||
return [CommandLineToArgv $cmdline] |
||||
} |
||||
|
||||
# Read an ini file int |
||||
proc twapi::read_inifile_key {section key args} { |
||||
array set opts [parseargs args { |
||||
{default.arg ""} |
||||
inifile.arg |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(inifile)]} { |
||||
set values [read_inifile_section $section -inifile $opts(inifile)] |
||||
} else { |
||||
set values [read_inifile_section $section] |
||||
} |
||||
|
||||
# Cannot use kl_get or arrays here because we want case insensitive compare |
||||
foreach {k val} $values { |
||||
if {[string equal -nocase $key $k]} { |
||||
return $val |
||||
} |
||||
} |
||||
return $opts(default) |
||||
} |
||||
|
||||
# Write an ini file string |
||||
proc twapi::write_inifile_key {section key value args} { |
||||
array set opts [parseargs args { |
||||
inifile.arg |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(inifile)]} { |
||||
WritePrivateProfileString $section $key $value $opts(inifile) |
||||
} else { |
||||
WriteProfileString $section $key $value |
||||
} |
||||
} |
||||
|
||||
# Delete an ini file string |
||||
proc twapi::delete_inifile_key {section key args} { |
||||
array set opts [parseargs args { |
||||
inifile.arg |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(inifile)]} { |
||||
WritePrivateProfileString $section $key $twapi::nullptr $opts(inifile) |
||||
} else { |
||||
WriteProfileString $section $key $twapi::nullptr |
||||
} |
||||
} |
||||
|
||||
# Get names of the sections in an inifile |
||||
proc twapi::read_inifile_section_names {args} { |
||||
array set opts [parseargs args { |
||||
inifile.arg |
||||
} -nulldefault -maxleftover 0] |
||||
|
||||
return [GetPrivateProfileSectionNames $opts(inifile)] |
||||
} |
||||
|
||||
# Get keys and values in a section in an inifile |
||||
proc twapi::read_inifile_section {section args} { |
||||
array set opts [parseargs args { |
||||
inifile.arg |
||||
} -nulldefault -maxleftover 0] |
||||
|
||||
set result [list ] |
||||
foreach line [GetPrivateProfileSection $section $opts(inifile)] { |
||||
set pos [string first "=" $line] |
||||
if {$pos >= 0} { |
||||
lappend result [string range $line 0 [expr {$pos-1}]] [string range $line [incr pos] end] |
||||
} |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
|
||||
# Delete an ini file section |
||||
proc twapi::delete_inifile_section {section args} { |
||||
variable nullptr |
||||
|
||||
array set opts [parseargs args { |
||||
inifile.arg |
||||
}] |
||||
|
||||
if {[info exists opts(inifile)]} { |
||||
WritePrivateProfileString $section $nullptr $nullptr $opts(inifile) |
||||
} else { |
||||
WriteProfileString $section $nullptr $nullptr |
||||
} |
||||
} |
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,254 @@
|
||||
# |
||||
# Copyright (c) 2004, 2008 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Clipboard related commands |
||||
|
||||
namespace eval twapi {} |
||||
|
||||
# Open the clipboard |
||||
# TBD - why no mechanism to pass window handle to OpenClipboard? |
||||
proc twapi::open_clipboard {} { |
||||
OpenClipboard 0 |
||||
} |
||||
|
||||
# Close the clipboard |
||||
proc twapi::close_clipboard {} { |
||||
catch {CloseClipboard} |
||||
return |
||||
} |
||||
|
||||
# Empty the clipboard |
||||
proc twapi::empty_clipboard {} { |
||||
EmptyClipboard |
||||
} |
||||
|
||||
proc twapi::_read_clipboard {fmt} { |
||||
# Always catch errors and close clipboard before passing exception on |
||||
# Also ensure memory unlocked |
||||
trap { |
||||
set h [GetClipboardData $fmt] |
||||
set p [GlobalLock $h] |
||||
set data [Twapi_ReadMemory 1 $p 0 [GlobalSize $h]] |
||||
} onerror {} { |
||||
catch {close_clipboard} |
||||
rethrow |
||||
} finally { |
||||
# If p exists, then we must have locked the handle |
||||
if {[info exists p]} { |
||||
GlobalUnlock $h |
||||
} |
||||
} |
||||
return $data |
||||
} |
||||
|
||||
proc twapi::read_clipboard {fmt} { |
||||
trap { |
||||
set data [_read_clipboard $fmt] |
||||
} onerror {TWAPI_WIN32 1418} { |
||||
# Caller did not have clipboard open. Do it on its behalf |
||||
open_clipboard |
||||
trap { |
||||
set data [_read_clipboard $fmt] |
||||
} finally { |
||||
catch {close_clipboard} |
||||
} |
||||
} |
||||
return $data |
||||
} |
||||
|
||||
# Read text data from the clipboard |
||||
proc twapi::read_clipboard_text {args} { |
||||
array set opts [parseargs args { |
||||
{raw.bool 0} |
||||
}] |
||||
|
||||
set bin [read_clipboard 13]; # 13 -> Unicode |
||||
# Decode Unicode and discard trailing nulls |
||||
set data [string trimright [encoding convertfrom unicode $bin] \0] |
||||
if {! $opts(raw)} { |
||||
set data [string map {"\r\n" "\n"} $data] |
||||
} |
||||
|
||||
return $data |
||||
} |
||||
|
||||
proc twapi::_write_clipboard {fmt data} { |
||||
# Always catch errors and close |
||||
# clipboard before passing exception on |
||||
trap { |
||||
# For byte arrays, string length does return correct size |
||||
# (DO NOT USE string bytelength - see Tcl docs!) |
||||
set len [string length $data] |
||||
|
||||
# Allocate global memory |
||||
set mem_h [GlobalAlloc 2 $len] |
||||
set mem_p [GlobalLock $mem_h] |
||||
|
||||
Twapi_WriteMemory 1 $mem_p 0 $len $data |
||||
|
||||
# The rest of this code just to ensure we do not free |
||||
# memory beyond this point irrespective of error/success |
||||
set h $mem_h |
||||
unset mem_p mem_h |
||||
GlobalUnlock $h |
||||
SetClipboardData $fmt $h |
||||
} onerror {} { |
||||
catch close_clipboard |
||||
rethrow |
||||
} finally { |
||||
if {[info exists mem_p]} { |
||||
GlobalUnlock $mem_h |
||||
} |
||||
if {[info exists mem_h]} { |
||||
GlobalFree $mem_h |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
proc twapi::write_clipboard {fmt data} { |
||||
trap { |
||||
_write_clipboard $fmt $data |
||||
} onerror {TWAPI_WIN32 1418} { |
||||
# Caller did not have clipboard open. Do it on its behalf |
||||
open_clipboard |
||||
empty_clipboard |
||||
trap { |
||||
_write_clipboard $fmt $data |
||||
} finally { |
||||
catch close_clipboard |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
# Write text to the clipboard |
||||
proc twapi::write_clipboard_text {data args} { |
||||
array set opts [parseargs args { |
||||
{raw.bool 0} |
||||
}] |
||||
|
||||
# Convert \n to \r\n leaving existing \r\n alone |
||||
if {! $opts(raw)} { |
||||
set data [regsub -all {(^|[^\r])\n} $data[set data ""] \\1\r\n] |
||||
} |
||||
append data \0 |
||||
write_clipboard 13 [encoding convertto unicode $data]; # 13 -> Unicode |
||||
return |
||||
} |
||||
|
||||
# Get current clipboard formats |
||||
proc twapi::get_clipboard_formats {} { |
||||
return [Twapi_EnumClipboardFormats] |
||||
} |
||||
|
||||
# Get registered clipboard format name. Clipboard does not have to be open |
||||
proc twapi::get_registered_clipboard_format_name {fmt} { |
||||
return [GetClipboardFormatName $fmt] |
||||
} |
||||
|
||||
# Register a clipboard format |
||||
proc twapi::register_clipboard_format {fmt_name} { |
||||
RegisterClipboardFormat $fmt_name |
||||
} |
||||
|
||||
# Returns 1/0 depending on whether a format is on the clipboard. Clipboard |
||||
# does not have to be open |
||||
proc twapi::clipboard_format_available {fmt} { |
||||
return [IsClipboardFormatAvailable $fmt] |
||||
} |
||||
|
||||
proc twapi::read_clipboard_paths {} { |
||||
set bin [read_clipboard 15] |
||||
# Extract the DROPFILES header |
||||
if {[binary scan $bin iiiii offset - - - unicode] != 5} { |
||||
error "Invalid or unsupported clipboard CF_DROP data." |
||||
} |
||||
# Sanity check |
||||
if {$offset >= [string length $bin]} { |
||||
error "Truncated clipboard data." |
||||
} |
||||
if {$unicode} { |
||||
set paths [encoding convertfrom unicode [string range $bin $offset end]] |
||||
} else { |
||||
set paths [encoding convertfrom ascii [string range $bin $offset end]] |
||||
} |
||||
set ret {} |
||||
foreach path [split $paths \0] { |
||||
if {[string length $path] == 0} break; # Empty string -> end of list |
||||
lappend ret [file join $path] |
||||
} |
||||
return $ret |
||||
} |
||||
|
||||
proc twapi::write_clipboard_paths {paths} { |
||||
# The header for a DROPFILES path list in hex |
||||
set fheader "1400000000000000000000000000000001000000" |
||||
set bin [binary format H* $fheader] |
||||
foreach path $paths { |
||||
# Note explicit \0 so the encoded binary includes the null terminator |
||||
append bin [encoding convertto unicode "[file nativename [file normalize $path]]\0"] |
||||
} |
||||
# A Unicode null char to terminate the list of paths |
||||
append bin [encoding convertto unicode \0] |
||||
write_clipboard 15 $bin |
||||
} |
||||
|
||||
# Start monitoring of the clipboard |
||||
proc twapi::_clipboard_handler {} { |
||||
variable _clipboard_monitors |
||||
|
||||
if {![info exists _clipboard_monitors] || |
||||
[llength $_clipboard_monitors] == 0} { |
||||
return; # Not an error, could have deleted while already queued |
||||
} |
||||
|
||||
foreach {id script} $_clipboard_monitors { |
||||
set code [catch {uplevel #0 $script} msg] |
||||
if {$code == 1} { |
||||
# Error - put in background but we do not abort |
||||
after 0 [list error $msg $::errorInfo $::errorCode] |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
proc twapi::start_clipboard_monitor {script} { |
||||
variable _clipboard_monitors |
||||
|
||||
set id "clip#[TwapiId]" |
||||
if {![info exists _clipboard_monitors] || |
||||
[llength $_clipboard_monitors] == 0} { |
||||
# No clipboard monitoring in progress. Start it |
||||
Twapi_ClipboardMonitorStart |
||||
} |
||||
|
||||
lappend _clipboard_monitors $id $script |
||||
return $id |
||||
} |
||||
|
||||
|
||||
|
||||
# Stop monitoring of the clipboard |
||||
proc twapi::stop_clipboard_monitor {clipid} { |
||||
variable _clipboard_monitors |
||||
|
||||
if {![info exists _clipboard_monitors]} { |
||||
return; # Should we raise an error instead? |
||||
} |
||||
|
||||
set new_monitors {} |
||||
foreach {id script} $_clipboard_monitors { |
||||
if {$id ne $clipid} { |
||||
lappend new_monitors $id $script |
||||
} |
||||
} |
||||
|
||||
set _clipboard_monitors $new_monitors |
||||
if {[llength $_clipboard_monitors] == 0} { |
||||
Twapi_ClipboardMonitorStop |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,736 @@
|
||||
# |
||||
# Copyright (c) 2004-2014, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi { |
||||
} |
||||
|
||||
# Allocate a new console |
||||
proc twapi::allocate_console {} { |
||||
AllocConsole |
||||
} |
||||
|
||||
# Free a console |
||||
proc twapi::free_console {} { |
||||
FreeConsole |
||||
} |
||||
|
||||
# Get a console handle |
||||
proc twapi::get_console_handle {type} { |
||||
switch -exact -- $type { |
||||
0 - |
||||
stdin { set fn "CONIN\$" } |
||||
1 - |
||||
stdout - |
||||
2 - |
||||
stderr { set fn "CONOUT\$" } |
||||
default { |
||||
error "Unknown console handle type '$type'" |
||||
} |
||||
} |
||||
|
||||
# 0xC0000000 -> GENERIC_READ | GENERIC_WRITE |
||||
# 3 -> FILE_SHARE_READ | FILE_SHARE_WRITE |
||||
# 3 -> OPEN_EXISTING |
||||
return [CreateFile $fn \ |
||||
0xC0000000 \ |
||||
3 \ |
||||
{{} 1} \ |
||||
3 \ |
||||
0 \ |
||||
NULL] |
||||
} |
||||
|
||||
proc twapi::_standard_handle_type {type} { |
||||
if {[string is integer -strict $type]} { |
||||
set type [format %d $type] ; # Convert hex etc. |
||||
} |
||||
switch -exact -- $type { |
||||
0 - |
||||
-10 - |
||||
stdin { set type -10 } |
||||
1 - |
||||
-11 - |
||||
stdout { set type -11 } |
||||
2 - |
||||
-12 - |
||||
stderr { set type -12 } |
||||
default { |
||||
error "Unknown console handle type '$type'" |
||||
} |
||||
} |
||||
return $type |
||||
} |
||||
|
||||
# Get a console handle |
||||
proc twapi::get_standard_handle {type} { |
||||
return [GetStdHandle [_standard_handle_type $type]] |
||||
} |
||||
|
||||
# Set a console handle |
||||
proc twapi::set_standard_handle {type handle} { |
||||
return [SetStdHandle [_standard_handle_type $type] $handle] |
||||
} |
||||
|
||||
proc twapi::_console_output_attr_to_flags {attrs} { |
||||
set flags 0 |
||||
foreach {attr bool} $attrs { |
||||
if {$bool} { |
||||
set flags [expr {$flags | [_console_output_attr $attr]}] |
||||
} |
||||
} |
||||
return $flags |
||||
} |
||||
|
||||
proc twapi::_flags_to_console_output_attr {flags} { |
||||
# Check for multiple bit attributes first, in order |
||||
set attrs {} |
||||
foreach attr { |
||||
-fgwhite -bgwhite -fggray -bggray |
||||
-fgturquoise -bgturquoise -fgpurple -bgpurple -fgyellow -bgyellow |
||||
-fgred -bgred -fggreen -bggreen -fgblue -bgblue |
||||
-fgbright -bgbright |
||||
} { |
||||
if {($flags & [_console_output_attr $attr]) == [_console_output_attr $attr]} { |
||||
lappend attrs $attr 1 |
||||
set flags [expr {$flags & ~ [_console_output_attr $attr]}] |
||||
if {$flags == 0} { |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $attrs |
||||
} |
||||
|
||||
|
||||
# Get the current mode settings for the console |
||||
proc twapi::_get_console_input_mode {conh} { |
||||
set mode [GetConsoleMode $conh] |
||||
return [_bitmask_to_switches $mode [_console_input_mode_syms]] |
||||
} |
||||
interp alias {} twapi::get_console_input_mode {} twapi::_do_console_proc twapi::_get_console_input_mode stdin |
||||
|
||||
# Get the current mode settings for the console |
||||
proc twapi::_get_console_output_mode {conh} { |
||||
set mode [GetConsoleMode $conh] |
||||
return [_bitmask_to_switches $mode [_console_output_mode_syms]] |
||||
} |
||||
interp alias {} twapi::get_console_output_mode {} twapi::_do_console_proc twapi::_get_console_output_mode stdout |
||||
|
||||
# Set console input mode |
||||
proc twapi::_set_console_input_mode {conh args} { |
||||
set mode [_switches_to_bitmask $args [_console_input_mode_syms]] |
||||
# If insertmode or quickedit mode are set, make sure to set extended bit |
||||
if {$mode & 0x60} { |
||||
setbits mode 0x80; # ENABLE_EXTENDED_FLAGS |
||||
} |
||||
|
||||
SetConsoleMode $conh $mode |
||||
} |
||||
interp alias {} twapi::set_console_input_mode {} twapi::_do_console_proc twapi::_set_console_input_mode stdin |
||||
|
||||
# Modify console input mode |
||||
proc twapi::_modify_console_input_mode {conh args} { |
||||
set prev [GetConsoleMode $conh] |
||||
set mode [_switches_to_bitmask $args [_console_input_mode_syms] $prev] |
||||
# If insertmode or quickedit mode are set, make sure to set extended bit |
||||
if {$mode & 0x60} { |
||||
setbits mode 0x80; # ENABLE_EXTENDED_FLAGS |
||||
} |
||||
|
||||
SetConsoleMode $conh $mode |
||||
# Returns the old modes |
||||
return [_bitmask_to_switches $prev [_console_input_mode_syms]] |
||||
} |
||||
interp alias {} twapi::modify_console_input_mode {} twapi::_do_console_proc twapi::_modify_console_input_mode stdin |
||||
|
||||
# |
||||
# Set console output mode |
||||
proc twapi::_set_console_output_mode {conh args} { |
||||
set mode [_switches_to_bitmask $args [_console_output_mode_syms]] |
||||
|
||||
SetConsoleMode $conh $mode |
||||
|
||||
} |
||||
interp alias {} twapi::set_console_output_mode {} twapi::_do_console_proc twapi::_set_console_output_mode stdout |
||||
|
||||
# Set console output mode |
||||
proc twapi::_modify_console_output_mode {conh args} { |
||||
set prev [GetConsoleMode $conh] |
||||
set mode [_switches_to_bitmask $args [_console_output_mode_syms] $prev] |
||||
|
||||
SetConsoleMode $conh $mode |
||||
# Returns the old modes |
||||
return [_bitmask_to_switches $prev [_console_output_mode_syms]] |
||||
} |
||||
interp alias {} twapi::modify_console_output_mode {} twapi::_do_console_proc twapi::_modify_console_output_mode stdout |
||||
|
||||
|
||||
# Create and return a handle to a screen buffer |
||||
proc twapi::create_console_screen_buffer {args} { |
||||
array set opts [parseargs args { |
||||
{inherit.bool 0} |
||||
{mode.arg readwrite {read write readwrite}} |
||||
{secd.arg ""} |
||||
{share.arg readwrite {none read write readwrite}} |
||||
} -maxleftover 0] |
||||
|
||||
switch -exact -- $opts(mode) { |
||||
read { set mode [_access_rights_to_mask generic_read] } |
||||
write { set mode [_access_rights_to_mask generic_write] } |
||||
readwrite { |
||||
set mode [_access_rights_to_mask {generic_read generic_write}] |
||||
} |
||||
} |
||||
switch -exact -- $opts(share) { |
||||
none { |
||||
set share 0 |
||||
} |
||||
read { |
||||
set share 1 ;# FILE_SHARE_READ |
||||
} |
||||
write { |
||||
set share 2 ;# FILE_SHARE_WRITE |
||||
} |
||||
readwrite { |
||||
set share 3 |
||||
} |
||||
} |
||||
|
||||
return [CreateConsoleScreenBuffer \ |
||||
$mode \ |
||||
$share \ |
||||
[_make_secattr $opts(secd) $opts(inherit)] \ |
||||
1] |
||||
} |
||||
|
||||
# Retrieve information about a console screen buffer |
||||
proc twapi::_get_console_screen_buffer_info {conh args} { |
||||
array set opts [parseargs args { |
||||
all |
||||
textattr |
||||
cursorpos |
||||
maxwindowsize |
||||
size |
||||
windowlocation |
||||
windowpos |
||||
windowsize |
||||
} -maxleftover 0] |
||||
|
||||
lassign [GetConsoleScreenBufferInfo $conh] size cursorpos textattr windowlocation maxwindowsize |
||||
|
||||
set result [list ] |
||||
foreach opt {size cursorpos maxwindowsize windowlocation} { |
||||
if {$opts($opt) || $opts(all)} { |
||||
lappend result -$opt [set $opt] |
||||
} |
||||
} |
||||
|
||||
if {$opts(windowpos) || $opts(all)} { |
||||
lappend result -windowpos [lrange $windowlocation 0 1] |
||||
} |
||||
|
||||
if {$opts(windowsize) || $opts(all)} { |
||||
lassign $windowlocation left top right bot |
||||
lappend result -windowsize [list [expr {$right-$left+1}] [expr {$bot-$top+1}]] |
||||
} |
||||
|
||||
if {$opts(textattr) || $opts(all)} { |
||||
lappend result -textattr [_flags_to_console_output_attr $textattr] |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
interp alias {} twapi::get_console_screen_buffer_info {} twapi::_do_console_proc twapi::_get_console_screen_buffer_info stdout |
||||
|
||||
# Set the cursor position |
||||
proc twapi::_set_console_cursor_position {conh pos} { |
||||
SetConsoleCursorPosition $conh $pos |
||||
} |
||||
interp alias {} twapi::set_console_cursor_position {} twapi::_do_console_proc twapi::_set_console_cursor_position stdout |
||||
|
||||
# Get the cursor position |
||||
proc twapi::get_console_cursor_position {conh} { |
||||
return [lindex [get_console_screen_buffer_info $conh -cursorpos] 1] |
||||
} |
||||
|
||||
# Write the specified string to the console |
||||
proc twapi::_console_write {conh s args} { |
||||
# Note writes are always in raw mode, |
||||
# TBD - support for scrolling |
||||
# TBD - support for attributes |
||||
|
||||
array set opts [parseargs args { |
||||
position.arg |
||||
{newlinemode.arg column {line column}} |
||||
{restoreposition.bool 0} |
||||
} -maxleftover 0] |
||||
|
||||
# Get screen buffer info including cursor position |
||||
array set csbi [get_console_screen_buffer_info $conh -cursorpos -size] |
||||
|
||||
# Get current console mode for later restoration |
||||
# If console is in processed mode, set it to raw mode |
||||
set oldmode [get_console_output_mode $conh] |
||||
set processed_index [lsearch -exact $oldmode "processed"] |
||||
if {$processed_index >= 0} { |
||||
# Console was in processed mode. Set it to raw mode |
||||
set newmode [lreplace $oldmode $processed_index $processed_index] |
||||
set_console_output_mode $conh $newmode |
||||
} |
||||
|
||||
trap { |
||||
# x,y are starting position to write |
||||
if {[info exists opts(position)]} { |
||||
lassign [_parse_integer_pair $opts(position)] x y |
||||
} else { |
||||
# No position specified, get current cursor position |
||||
lassign $csbi(-cursorpos) x y |
||||
} |
||||
|
||||
set startx [expr {$opts(newlinemode) == "column" ? $x : 0}] |
||||
|
||||
# Get screen buffer limits |
||||
lassign $csbi(-size) width height |
||||
|
||||
# Ensure line terminations are just \n |
||||
set s [string map [list \r\n \n] $s] |
||||
|
||||
# Write out each line at ($x,$y) |
||||
# Either \r or \n is considered a newline |
||||
foreach line [split $s \r\n] { |
||||
if {$y >= $height} break |
||||
set_console_cursor_position $conh [list $x $y] |
||||
if {$x < $width} { |
||||
# Write the characters - do not write more than buffer width |
||||
set num_chars [expr {$width-$x}] |
||||
if {[string length $line] < $num_chars} { |
||||
set num_chars [string length $line] |
||||
} |
||||
WriteConsole $conh $line $num_chars |
||||
} |
||||
|
||||
|
||||
# Calculate starting position of next line |
||||
incr y |
||||
set x $startx |
||||
} |
||||
|
||||
} finally { |
||||
# Restore cursor if requested |
||||
if {$opts(restoreposition)} { |
||||
set_console_cursor_position $conh $csbi(-cursorpos) |
||||
} |
||||
# Restore output mode if changed |
||||
if {[info exists newmode]} { |
||||
set_console_output_mode $conh $oldmode |
||||
} |
||||
} |
||||
|
||||
return |
||||
} |
||||
interp alias {} twapi::write_console {} twapi::_do_console_proc twapi::_console_write stdout |
||||
interp alias {} twapi::console_write {} twapi::_do_console_proc twapi::_console_write stdout |
||||
|
||||
# Fill an area of the console with the specified attribute |
||||
proc twapi::_fill_console {conh args} { |
||||
array set opts [parseargs args { |
||||
position.arg |
||||
numlines.int |
||||
numcols.int |
||||
{mode.arg column {line column}} |
||||
window.bool |
||||
fillchar.arg |
||||
} -ignoreunknown] |
||||
|
||||
# args will now contain attribute switches if any |
||||
set attr [_console_output_attr_to_flags $args] |
||||
|
||||
# Get screen buffer info for window and size of buffer |
||||
array set csbi [get_console_screen_buffer_info $conh -windowpos -windowsize -size] |
||||
# Height and width of the console |
||||
lassign $csbi(-size) conx cony |
||||
|
||||
# Figure out what area we want to fill |
||||
# startx,starty are starting position to write |
||||
# sizex, sizey are the number of rows/lines |
||||
if {[info exists opts(window)]} { |
||||
if {[info exists opts(numlines)] || [info exists opts(numcols)] |
||||
|| [info exists opts(position)]} { |
||||
error "Option -window cannot be used togther with options -position, -numlines or -numcols" |
||||
} |
||||
lassign [_parse_integer_pair $csbi(-windowpos)] startx starty |
||||
lassign [_parse_integer_pair $csbi(-windowsize)] sizex sizey |
||||
} else { |
||||
if {[info exists opts(position)]} { |
||||
lassign [_parse_integer_pair $opts(position)] startx starty |
||||
} else { |
||||
set startx 0 |
||||
set starty 0 |
||||
} |
||||
if {[info exists opts(numlines)]} { |
||||
set sizey $opts(numlines) |
||||
} else { |
||||
set sizey $cony |
||||
} |
||||
if {[info exists opts(numcols)]} { |
||||
set sizex $opts(numcols) |
||||
} else { |
||||
set sizex [expr {$conx - $startx}] |
||||
} |
||||
} |
||||
|
||||
set firstcol [expr {$opts(mode) == "column" ? $startx : 0}] |
||||
|
||||
# Fill attribute at ($x,$y) |
||||
set x $startx |
||||
set y $starty |
||||
while {$y < $cony && $y < ($starty + $sizey)} { |
||||
if {$x < $conx} { |
||||
# Write the characters - do not write more than buffer width |
||||
set max [expr {$conx-$x}] |
||||
if {[info exists attr]} { |
||||
FillConsoleOutputAttribute $conh $attr [expr {$sizex > $max ? $max : $sizex}] [list $x $y] |
||||
} |
||||
if {[info exists opts(fillchar)]} { |
||||
FillConsoleOutputCharacter $conh $opts(fillchar) [expr {$sizex > $max ? $max : $sizex}] [list $x $y] |
||||
} |
||||
} |
||||
|
||||
# Calculate starting position of next line |
||||
incr y |
||||
set x $firstcol |
||||
} |
||||
|
||||
return |
||||
} |
||||
interp alias {} twapi::fill_console {} twapi::_do_console_proc twapi::_fill_console stdout |
||||
|
||||
# Clear the console |
||||
proc twapi::_clear_console {conh args} { |
||||
# I support we could just call fill_console but this code was already |
||||
# written and is faster |
||||
array set opts [parseargs args { |
||||
{fillchar.arg " "} |
||||
{windowonly.bool 0} |
||||
} -maxleftover 0] |
||||
|
||||
array set cinfo [get_console_screen_buffer_info $conh -size -windowpos -windowsize] |
||||
lassign $cinfo(-size) width height |
||||
if {$opts(windowonly)} { |
||||
# Only clear portion visible in the window. We have to do this |
||||
# line by line since we do not want to erase text scrolled off |
||||
# the window either in the vertical or horizontal direction |
||||
lassign $cinfo(-windowpos) x y |
||||
lassign $cinfo(-windowsize) w h |
||||
for {set i 0} {$i < $h} {incr i} { |
||||
FillConsoleOutputCharacter \ |
||||
$conh \ |
||||
$opts(fillchar) \ |
||||
$w \ |
||||
[list $x [expr {$y+$i}]] |
||||
} |
||||
} else { |
||||
FillConsoleOutputCharacter \ |
||||
$conh \ |
||||
$opts(fillchar) \ |
||||
[expr {($width*$height) }] \ |
||||
[list 0 0] |
||||
} |
||||
return |
||||
} |
||||
interp alias {} twapi::clear_console {} twapi::_do_console_proc twapi::_clear_console stdout |
||||
# |
||||
# Flush console input |
||||
proc twapi::_flush_console_input {conh} { |
||||
FlushConsoleInputBuffer $conh |
||||
} |
||||
interp alias {} twapi::flush_console_input {} twapi::_do_console_proc twapi::_flush_console_input stdin |
||||
|
||||
# Return number of pending console input events |
||||
proc twapi::_get_console_pending_input_count {conh} { |
||||
return [GetNumberOfConsoleInputEvents $conh] |
||||
} |
||||
interp alias {} twapi::get_console_pending_input_count {} twapi::_do_console_proc twapi::_get_console_pending_input_count stdin |
||||
|
||||
# Generate a console control event |
||||
proc twapi::generate_console_control_event {event {procgrp 0}} { |
||||
switch -exact -- $event { |
||||
ctrl-c {set event 0} |
||||
ctrl-break {set event 1} |
||||
default {error "Invalid event definition '$event'"} |
||||
} |
||||
GenerateConsoleCtrlEvent $event $procgrp |
||||
} |
||||
|
||||
# Get number of mouse buttons |
||||
proc twapi::num_console_mouse_buttons {} { |
||||
return [GetNumberOfConsoleMouseButtons] |
||||
} |
||||
|
||||
# Get console title text |
||||
proc twapi::get_console_title {} { |
||||
return [GetConsoleTitle] |
||||
} |
||||
|
||||
# Set console title text |
||||
proc twapi::set_console_title {title} { |
||||
return [SetConsoleTitle $title] |
||||
} |
||||
|
||||
# Get the handle to the console window |
||||
proc twapi::get_console_window {} { |
||||
return [GetConsoleWindow] |
||||
} |
||||
|
||||
# Get the largest console window size |
||||
proc twapi::_get_console_window_maxsize {conh} { |
||||
return [GetLargestConsoleWindowSize $conh] |
||||
} |
||||
interp alias {} twapi::get_console_window_maxsize {} twapi::_do_console_proc twapi::_get_console_window_maxsize stdout |
||||
|
||||
proc twapi::_set_console_active_screen_buffer {conh} { |
||||
SetConsoleActiveScreenBuffer $conh |
||||
} |
||||
interp alias {} twapi::set_console_active_screen_buffer {} twapi::_do_console_proc twapi::_set_console_active_screen_buffer stdout |
||||
|
||||
# Set the size of the console screen buffer |
||||
proc twapi::_set_console_screen_buffer_size {conh size} { |
||||
SetConsoleScreenBufferSize $conh [_parse_integer_pair $size] |
||||
} |
||||
interp alias {} twapi::set_console_screen_buffer_size {} twapi::_do_console_proc twapi::_set_console_screen_buffer_size stdout |
||||
|
||||
# Set the default text attribute |
||||
proc twapi::_set_console_default_attr {conh args} { |
||||
SetConsoleTextAttribute $conh [_console_output_attr_to_flags $args] |
||||
} |
||||
interp alias {} twapi::set_console_default_attr {} twapi::_do_console_proc twapi::_set_console_default_attr stdout |
||||
|
||||
# Set the console window position |
||||
proc twapi::_set_console_window_location {conh rect args} { |
||||
array set opts [parseargs args { |
||||
{absolute.bool true} |
||||
} -maxleftover 0] |
||||
|
||||
SetConsoleWindowInfo $conh $opts(absolute) $rect |
||||
} |
||||
interp alias {} twapi::set_console_window_location {} twapi::_do_console_proc twapi::_set_console_window_location stdout |
||||
|
||||
proc twapi::get_console_window_location {conh} { |
||||
return [lindex [get_console_screen_buffer_info $conh -windowlocation] 1] |
||||
} |
||||
|
||||
# Get the console code page |
||||
proc twapi::get_console_output_codepage {} { |
||||
return [GetConsoleOutputCP] |
||||
} |
||||
|
||||
# Set the console code page |
||||
proc twapi::set_console_output_codepage {cp} { |
||||
SetConsoleOutputCP $cp |
||||
} |
||||
|
||||
# Get the console input code page |
||||
proc twapi::get_console_input_codepage {} { |
||||
return [GetConsoleCP] |
||||
} |
||||
|
||||
# Set the console input code page |
||||
proc twapi::set_console_input_codepage {cp} { |
||||
SetConsoleCP $cp |
||||
} |
||||
|
||||
# Read a line of input |
||||
proc twapi::_console_read {conh args} { |
||||
if {[llength $args]} { |
||||
set oldmode [modify_console_input_mode $conh {*}$args] |
||||
} |
||||
trap { |
||||
return [ReadConsole $conh 1024] |
||||
} finally { |
||||
if {[info exists oldmode]} { |
||||
set_console_input_mode $conh {*}$oldmode |
||||
} |
||||
} |
||||
} |
||||
interp alias {} twapi::console_read {} twapi::_do_console_proc twapi::_console_read stdin |
||||
|
||||
proc twapi::_map_console_controlkeys {control} { |
||||
return [_make_symbolic_bitmask $control { |
||||
capslock 0x80 |
||||
enhanced 0x100 |
||||
leftalt 0x2 |
||||
leftctrl 0x8 |
||||
numlock 0x20 |
||||
rightalt 0x1 |
||||
rightctrl 4 |
||||
scrolllock 0x40 |
||||
shift 0x10 |
||||
} 0] |
||||
} |
||||
|
||||
proc twapi::_console_read_input_records {conh args} { |
||||
parseargs args { |
||||
{count.int 1} |
||||
peek |
||||
} -setvars -maxleftover 0 |
||||
set recs {} |
||||
if {$peek} { |
||||
set input [PeekConsoleInput $conh $count] |
||||
} else { |
||||
set input [ReadConsoleInput $conh $count] |
||||
} |
||||
foreach rec $input { |
||||
switch [format %d [lindex $rec 0]] { |
||||
1 { |
||||
lassign [lindex $rec 1] keydown repeat keycode scancode char controlstate |
||||
lappend recs \ |
||||
[list key [list \ |
||||
keystate [expr {$keydown ? "down" : "up"}] \ |
||||
repeat $repeat keycode $keycode \ |
||||
scancode $scancode char $char \ |
||||
controls [_map_console_controlkeys $controlstate]]] |
||||
} |
||||
2 { |
||||
lassign [lindex $rec 1] position buttonstate controlstate flags |
||||
set buttons {} |
||||
if {[expr {$buttonstate & 0x1}]} {lappend buttons left} |
||||
if {[expr {$buttonstate & 0x2}]} {lappend buttons right} |
||||
if {[expr {$buttonstate & 0x4}]} {lappend buttons left2} |
||||
if {[expr {$buttonstate & 0x8}]} {lappend buttons left3} |
||||
if {[expr {$buttonstate & 0x10}]} {lappend buttons left4} |
||||
if {$flags & 0x8} { |
||||
set horizontalwheel [expr {$buttonstate >> 16}] |
||||
} else { |
||||
set horizontalwheel 0 |
||||
} |
||||
if {$flags & 0x4} { |
||||
set verticalwheel [expr {$buttonstate >> 16}] |
||||
} else { |
||||
set verticalwheel 0 |
||||
} |
||||
lappend recs \ |
||||
[list mouse [list \ |
||||
position $position \ |
||||
buttons $buttons \ |
||||
controls [_map_console_controlkeys $controlstate] \ |
||||
doubleclick [expr {$flags & 0x2}] \ |
||||
horizontalwheel $horizontalwheel \ |
||||
moved [expr {$flags & 0x1}] \ |
||||
verticalwheel $verticalwheel]] |
||||
} |
||||
default { |
||||
lappend recs [list \ |
||||
[dict* {4 buffersize 8 menu 16 focus} [lindex $rec 0]] \ |
||||
[lindex $rec 1]] |
||||
} |
||||
} |
||||
} |
||||
return $recs |
||||
} |
||||
interp alias {} twapi::console_read_input_records {} twapi::_do_console_proc twapi::_console_read_input_records stdin |
||||
|
||||
# Set up a console handler |
||||
proc twapi::_console_ctrl_handler {ctrl} { |
||||
variable _console_control_script |
||||
if {[info exists _console_control_script]} { |
||||
return [uplevel #0 [linsert $_console_control_script end $ctrl]] |
||||
} |
||||
return 0; # Not handled |
||||
} |
||||
proc twapi::set_console_control_handler {script} { |
||||
variable _console_control_script |
||||
if {[string length $script]} { |
||||
if {![info exists _console_control_script]} { |
||||
Twapi_ConsoleEventNotifier 1 |
||||
} |
||||
set _console_control_script $script |
||||
} else { |
||||
if {[info exists _console_control_script]} { |
||||
Twapi_ConsoleEventNotifier 0 |
||||
unset _console_control_script |
||||
} |
||||
} |
||||
} |
||||
|
||||
# |
||||
# Utilities |
||||
# |
||||
|
||||
# Helper to call a proc after doing a stdin/stdout/stderr -> handle |
||||
# mapping. The handle is closed after calling the proc. The first |
||||
# arg in $args must be the console handle if $args is not an empty list |
||||
proc twapi::_do_console_proc {proc default args} { |
||||
if {[llength $args] == 0} { |
||||
set args [list $default] |
||||
} |
||||
set conh [lindex $args 0] |
||||
switch -exact -- [string tolower $conh] { |
||||
stdin - |
||||
stdout - |
||||
stderr { |
||||
set real_handle [get_console_handle $conh] |
||||
trap { |
||||
lset args 0 $real_handle |
||||
return [uplevel 1 [list $proc] $args] |
||||
} finally { |
||||
CloseHandle $real_handle |
||||
} |
||||
} |
||||
} |
||||
|
||||
return [uplevel 1 [list $proc] $args] |
||||
} |
||||
|
||||
proc twapi::_console_input_mode_syms {} { |
||||
return { |
||||
-processedinput 0x0001 |
||||
-lineinput 0x0002 |
||||
-echoinput 0x0004 |
||||
-windowinput 0x0008 |
||||
-mouseinput 0x0010 |
||||
-insertmode 0x0020 |
||||
-quickeditmode 0x0040 |
||||
-extendedmode 0x0080 |
||||
-autoposition 0x0100 |
||||
} |
||||
} |
||||
|
||||
proc twapi::_console_output_mode_syms {} { |
||||
return { -processedoutput 1 -wrapoutput 2 } |
||||
} |
||||
|
||||
twapi::proc* twapi::_console_output_attr {sym} { |
||||
variable _console_output_attr_syms |
||||
array set _console_output_attr_syms { |
||||
-fgblue 1 |
||||
-fggreen 2 |
||||
-fgturquoise 3 |
||||
-fgred 4 |
||||
-fgpurple 5 |
||||
-fgyellow 6 |
||||
-fggray 7 |
||||
-fgbright 8 |
||||
-fgwhite 15 |
||||
-bgblue 16 |
||||
-bggreen 32 |
||||
-bgturquoise 48 |
||||
-bgred 64 |
||||
-bgpurple 80 |
||||
-bgyellow 96 |
||||
-bggray 112 |
||||
-bgbright 128 |
||||
-bgwhite 240 |
||||
} |
||||
} { |
||||
variable _console_output_attr_syms |
||||
if {[info exists _console_output_attr_syms($sym)]} { |
||||
return $_console_output_attr_syms($sym) |
||||
} |
||||
|
||||
badargs! "Invalid console output attribute '$sym'" 3 |
||||
} |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,624 @@
|
||||
# |
||||
# Copyright (c) 2008-2014 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi { |
||||
struct _PREVENT_MEDIA_REMOVAL { |
||||
BOOLEAN PreventMediaRemoval; |
||||
} |
||||
record device_element { class_guid device_instance reserved } |
||||
} |
||||
|
||||
interp alias {} close_devinfoset {} devinfoset_close |
||||
|
||||
proc twapi::rescan_devices {} { |
||||
CM_Reenumerate_DevNode_Ex [CM_Locate_DevNode_Ex "" 0] 0 |
||||
} |
||||
|
||||
|
||||
# Callback invoked for device changes. |
||||
# Does some processing of passed data and then invokes the |
||||
# real callback script |
||||
proc twapi::_device_notification_handler {id args} { |
||||
variable _device_notifiers |
||||
set idstr "devnotifier#$id" |
||||
if {![info exists _device_notifiers($idstr)]} { |
||||
# Notifications that expect a response default to "true" |
||||
return 1 |
||||
} |
||||
set script [lindex $_device_notifiers($idstr) 1] |
||||
|
||||
# For volume notifications, change drive bitmask to |
||||
# list of drives before passing back to script |
||||
set event [lindex $args 0] |
||||
if {[lindex $args 1] eq "volume" && |
||||
($event eq "deviceremovecomplete" || $event eq "devicearrival")} { |
||||
lset args 2 [_drivemask_to_drivelist [lindex $args 2]] |
||||
|
||||
# Also indicate whether network volume and whether change is a media |
||||
# change or physical change |
||||
set attrs [list ] |
||||
set flags [lindex $args 3] |
||||
if {$flags & 1} { |
||||
lappend attrs mediachange |
||||
} |
||||
if {$flags & 2} { |
||||
lappend attrs networkvolume |
||||
} |
||||
lset args 3 $attrs |
||||
} |
||||
|
||||
return [uplevel #0 [linsert $script end $idstr {*}$args]] |
||||
} |
||||
|
||||
proc twapi::start_device_notifier {script args} { |
||||
variable _device_notifiers |
||||
|
||||
set script [lrange $script 0 end]; # Verify syntactically a list |
||||
|
||||
array set opts [parseargs args { |
||||
deviceinterface.arg |
||||
handle.arg |
||||
} -maxleftover 0] |
||||
|
||||
# For reference - some common device interface classes |
||||
# NOTE: NOT ALL HAVE BEEN VERIFIED! |
||||
# Network Card {ad498944-762f-11d0-8dcb-00c04fc3358c} |
||||
# Human Interface Device (HID) {4d1e55b2-f16f-11cf-88cb-001111000030} |
||||
# GUID_DEVINTERFACE_DISK - {53f56307-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_CDROM - {53f56308-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_PARTITION - {53f5630a-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_TAPE - {53f5630b-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_WRITEONCEDISK - {53f5630c-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_VOLUME - {53f5630d-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_MEDIUMCHANGER - {53f56310-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_FLOPPY - {53f56311-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_CDCHANGER - {53f56312-b6bf-11d0-94f2-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_STORAGEPORT - {2accfe60-c130-11d2-b082-00a0c91efb8b} |
||||
# GUID_DEVINTERFACE_KEYBOARD - {884b96c3-56ef-11d1-bc8c-00a0c91405dd} |
||||
# GUID_DEVINTERFACE_MOUSE - {378de44c-56ef-11d1-bc8c-00a0c91405dd} |
||||
# GUID_DEVINTERFACE_PARALLEL - {97F76EF0-F883-11D0-AF1F-0000F800845C} |
||||
# GUID_DEVINTERFACE_COMPORT - {86e0d1e0-8089-11d0-9ce4-08003e301f73} |
||||
# GUID_DEVINTERFACE_DISPLAY_ADAPTER - {5b45201d-f2f2-4f3b-85bb-30ff1f953599} |
||||
# GUID_DEVINTERFACE_USB_HUB - {f18a0e88-c30c-11d0-8815-00a0c906bed8} |
||||
# GUID_DEVINTERFACE_USB_DEVICE - {A5DCBF10-6530-11D2-901F-00C04FB951ED} |
||||
# GUID_DEVINTERFACE_USB_HOST_CONTROLLER - {3abf6f2d-71c4-462a-8a92-1e6861e6af27} |
||||
|
||||
|
||||
if {[info exists opts(deviceinterface)] && [info exists opts(handle)]} { |
||||
error "Options -deviceinterface and -handle are mutually exclusive." |
||||
} |
||||
|
||||
if {![info exists opts(deviceinterface)]} { |
||||
set opts(deviceinterface) "" |
||||
} |
||||
if {[info exists opts(handle)]} { |
||||
set type 6 |
||||
} else { |
||||
set opts(handle) NULL |
||||
switch -exact -- $opts(deviceinterface) { |
||||
port { set type 3 ; set opts(deviceinterface) "" } |
||||
volume { set type 2 ; set opts(deviceinterface) "" } |
||||
default { |
||||
# device interface class guid or empty string (for all device interfaces) |
||||
set type 5 |
||||
} |
||||
} |
||||
} |
||||
|
||||
set id [Twapi_RegisterDeviceNotification $type $opts(deviceinterface) $opts(handle)] |
||||
set idstr "devnotifier#$id" |
||||
|
||||
set _device_notifiers($idstr) [list $id $script] |
||||
return $idstr |
||||
} |
||||
|
||||
proc twapi::stop_device_notifier {idstr} { |
||||
variable _device_notifiers |
||||
|
||||
if {![info exists _device_notifiers($idstr)]} { |
||||
return; |
||||
} |
||||
|
||||
Twapi_UnregisterDeviceNotification [lindex $_device_notifiers($idstr) 0] |
||||
unset _device_notifiers($idstr) |
||||
} |
||||
|
||||
proc twapi::devinfoset {args} { |
||||
array set opts [parseargs args { |
||||
{guid.arg ""} |
||||
{classtype.arg setup {interface setup}} |
||||
{presentonly.bool false 0x2} |
||||
{currentprofileonly.bool false 0x8} |
||||
{deviceinfoset.arg NULL} |
||||
{hwin.int 0} |
||||
{system.arg ""} |
||||
{pnpenumerator.arg ""} |
||||
} -maxleftover 0] |
||||
|
||||
# DIGCF_ALLCLASSES is bitmask 4 |
||||
set flags [expr {$opts(guid) eq "" ? 0x4 : 0}] |
||||
if {$opts(classtype) eq "interface"} { |
||||
if {$opts(pnpenumerator) ne ""} { |
||||
error "The -pnpenumerator option cannot be used when -classtype interface is specified." |
||||
} |
||||
# DIGCF_DEVICEINTERFACE |
||||
set flags [expr {$flags | 0x10}] |
||||
} |
||||
|
||||
# DIGCF_PRESENT |
||||
set flags [expr {$flags | $opts(presentonly)}] |
||||
|
||||
# DIGCF_PRESENT |
||||
set flags [expr {$flags | $opts(currentprofileonly)}] |
||||
|
||||
return [SetupDiGetClassDevsEx \ |
||||
$opts(guid) \ |
||||
$opts(pnpenumerator) \ |
||||
$opts(hwin) \ |
||||
$flags \ |
||||
$opts(deviceinfoset) \ |
||||
$opts(system)] |
||||
} |
||||
|
||||
|
||||
# Given a device information set, returns the device elements within it |
||||
proc twapi::devinfoset_elements {hdevinfo} { |
||||
set result [list ] |
||||
set i 0 |
||||
trap { |
||||
while {true} { |
||||
lappend result [SetupDiEnumDeviceInfo $hdevinfo $i] |
||||
incr i |
||||
} |
||||
} onerror {TWAPI_WIN32 0x103} { |
||||
# Fine, Just means no more items |
||||
} onerror {TWAPI_WIN32 0x80070103} { |
||||
# Fine, Just means no more items (HRESULT version of above code) |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
# Given a device information set, returns the device elements within it |
||||
proc twapi::devinfoset_instance_ids {hdevinfo} { |
||||
set result [list ] |
||||
set i 0 |
||||
trap { |
||||
while {true} { |
||||
lappend result [device_element_instance_id $hdevinfo [SetupDiEnumDeviceInfo $hdevinfo $i]] |
||||
incr i |
||||
} |
||||
} onerror {TWAPI_WIN32 0x103} { |
||||
# Fine, Just means no more items |
||||
} onerror {TWAPI_WIN32 0x80070103} { |
||||
# Fine, Just means no more items (HRESULT version of above code) |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
# Returns a device instance element from a devinfoset |
||||
proc twapi::devinfoset_element {hdevinfo instance_id} { |
||||
return [SetupDiOpenDeviceInfo $hdevinfo $instance_id 0 0] |
||||
} |
||||
|
||||
# Get the registry property for a devinfoset element |
||||
proc twapi::devinfoset_element_registry_property {hdevinfo develem prop} { |
||||
Twapi_SetupDiGetDeviceRegistryProperty $hdevinfo $develem [_device_registry_sym_to_code $prop] |
||||
} |
||||
|
||||
# Given a device information set, returns a list of specified registry |
||||
# properties for all elements of the set |
||||
# args is list of properties to retrieve |
||||
proc twapi::devinfoset_registry_properties {hdevinfo args} { |
||||
set result [list ] |
||||
trap { |
||||
# Keep looping until there is an error saying no more items |
||||
set i 0 |
||||
while {true} { |
||||
|
||||
# First element is the DEVINFO_DATA element |
||||
set devinfo_data [SetupDiEnumDeviceInfo $hdevinfo $i] |
||||
set item [list -deviceelement $devinfo_data ] |
||||
|
||||
# Get all specified property values |
||||
foreach prop $args { |
||||
set intprop [_device_registry_sym_to_code $prop] |
||||
trap { |
||||
lappend item $prop \ |
||||
[list success \ |
||||
[Twapi_SetupDiGetDeviceRegistryProperty \ |
||||
$hdevinfo $devinfo_data $intprop]] |
||||
} onerror {} { |
||||
lappend item $prop [list fail [list [trapresult] $::errorCode]] |
||||
} |
||||
} |
||||
lappend result $item |
||||
|
||||
incr i |
||||
} |
||||
} onerror {TWAPI_WIN32 0x103} { |
||||
# Fine, Just means no more items |
||||
} onerror {TWAPI_WIN32 0x80070103} { |
||||
# Fine, Just means no more items (HRESULT version of above code) |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
|
||||
# Given a device information set, returns specified device interface |
||||
# properties |
||||
# TBD - document ? |
||||
proc twapi::devinfoset_interface_details {hdevinfo guid args} { |
||||
set result [list ] |
||||
|
||||
array set opts [parseargs args { |
||||
{matchdeviceelement.arg {}} |
||||
interfaceclass |
||||
flags |
||||
devicepath |
||||
deviceelement |
||||
ignoreerrors |
||||
} -maxleftover 0] |
||||
|
||||
trap { |
||||
# Keep looping until there is an error saying no more items |
||||
set i 0 |
||||
while {true} { |
||||
set interface_data [SetupDiEnumDeviceInterfaces $hdevinfo \ |
||||
$opts(matchdeviceelement) $guid $i] |
||||
set item [list ] |
||||
if {$opts(interfaceclass)} { |
||||
lappend item -interfaceclass [lindex $interface_data 0] |
||||
} |
||||
if {$opts(flags)} { |
||||
set flags [lindex $interface_data 1] |
||||
set symflags [_make_symbolic_bitmask $flags {active 1 default 2 removed 4} false] |
||||
lappend item -flags [linsert $symflags 0 $flags] |
||||
} |
||||
|
||||
if {$opts(devicepath) || $opts(deviceelement)} { |
||||
# Need to get device interface detail. |
||||
trap { |
||||
foreach {devicepath deviceelement} \ |
||||
[SetupDiGetDeviceInterfaceDetail \ |
||||
$hdevinfo \ |
||||
$interface_data \ |
||||
$opts(matchdeviceelement)] \ |
||||
break |
||||
|
||||
if {$opts(deviceelement)} { |
||||
lappend item -deviceelement $deviceelement |
||||
} |
||||
if {$opts(devicepath)} { |
||||
lappend item -devicepath $devicepath |
||||
} |
||||
} onerror {} { |
||||
if {! $opts(ignoreerrors)} { |
||||
rethrow |
||||
} |
||||
} |
||||
} |
||||
lappend result $item |
||||
|
||||
incr i |
||||
} |
||||
} onerror {TWAPI_WIN32 0x103} { |
||||
# Fine, Just means no more items |
||||
} onerror {TWAPI_WIN32 0x80070103} { |
||||
# Fine, Just means no more items (HRESULT version of above code) |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
|
||||
# Return the guids associated with a device class set name. Note |
||||
# the latter is not unique so multiple guids may be associated. |
||||
proc twapi::device_setup_class_name_to_guids {name args} { |
||||
array set opts [parseargs args { |
||||
system.arg |
||||
} -maxleftover 0 -nulldefault] |
||||
|
||||
return [twapi::SetupDiClassGuidsFromNameEx $name $opts(system)] |
||||
} |
||||
|
||||
# Utility functions |
||||
|
||||
proc twapi::_init_device_registry_code_maps {} { |
||||
variable _device_registry_syms |
||||
variable _device_registry_codes |
||||
|
||||
# Note this list is ordered based on the corresponding integer codes |
||||
set _device_registry_code_syms { |
||||
devicedesc hardwareid compatibleids unused0 service unused1 |
||||
unused2 class classguid driver configflags mfg friendlyname |
||||
location_information physical_device_object_name capabilities |
||||
ui_number upperfilters lowerfilters |
||||
bustypeguid legacybustype busnumber enumerator_name security |
||||
security_sds devtype exclusive characteristics address |
||||
ui_number_desc_format device_power_data |
||||
removal_policy removal_policy_hw_default removal_policy_override |
||||
install_state location_paths base_containerid |
||||
} |
||||
|
||||
set i 0 |
||||
foreach sym $_device_registry_code_syms { |
||||
set _device_registry_codes($sym) $i |
||||
incr i |
||||
} |
||||
} |
||||
|
||||
# Map a device registry property to a symbol |
||||
proc twapi::_device_registry_code_to_sym {code} { |
||||
_init_device_registry_code_maps |
||||
|
||||
# Once we have initialized, redefine ourselves so we do not do so |
||||
# every time. Note define at global ::twapi scope! |
||||
proc ::twapi::_device_registry_code_to_sym {code} { |
||||
variable _device_registry_code_syms |
||||
if {$code >= [llength $_device_registry_code_syms]} { |
||||
return $code |
||||
} else { |
||||
return [lindex $_device_registry_code_syms $code] |
||||
} |
||||
} |
||||
# Call the redefined proc |
||||
return [_device_registry_code_to_sym $code] |
||||
} |
||||
|
||||
# Map a device registry property symbol to a numeric code |
||||
proc twapi::_device_registry_sym_to_code {sym} { |
||||
_init_device_registry_code_maps |
||||
|
||||
# Once we have initialized, redefine ourselves so we do not do so |
||||
# every time. Note define at global ::twapi scope! |
||||
proc ::twapi::_device_registry_sym_to_code {sym} { |
||||
variable _device_registry_codes |
||||
# Return the value. If non-existent, an error will be raised |
||||
if {[info exists _device_registry_codes($sym)]} { |
||||
return $_device_registry_codes($sym) |
||||
} elseif {[string is integer -strict $sym]} { |
||||
return $sym |
||||
} else { |
||||
error "Unknown or unsupported device registry property symbol '$sym'" |
||||
} |
||||
} |
||||
# Call the redefined proc |
||||
return [_device_registry_sym_to_code $sym] |
||||
} |
||||
|
||||
# Do a device ioctl, returning result as a binary |
||||
# TBD - document that caller has to handle errors 122 (ERROR_INSUFFICIENT_BUFFER) and (ERROR_MORE_DATA) |
||||
proc twapi::device_ioctl {h code args} { |
||||
array set opts [parseargs args { |
||||
{input.arg {}} |
||||
{outputcount.int 0} |
||||
} -maxleftover 0] |
||||
|
||||
return [DeviceIoControl $h $code $opts(input) $opts(outputcount)] |
||||
} |
||||
|
||||
|
||||
# Return a list of physical disks. Note CD-ROMs and floppies not included |
||||
proc twapi::find_physical_disks {} { |
||||
# Disk interface class guid |
||||
set guid {{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}} |
||||
set hdevinfo [devinfoset \ |
||||
-guid $guid \ |
||||
-presentonly true \ |
||||
-classtype interface] |
||||
trap { |
||||
return [kl_flatten [devinfoset_interface_details $hdevinfo $guid -devicepath] -devicepath] |
||||
} finally { |
||||
devinfoset_close $hdevinfo |
||||
} |
||||
} |
||||
|
||||
# Return information about a physical disk |
||||
proc twapi::get_physical_disk_info {disk args} { |
||||
set result [list ] |
||||
|
||||
array set opts [parseargs args { |
||||
geometry |
||||
layout |
||||
all |
||||
} -maxleftover 0] |
||||
|
||||
if {$opts(all) || $opts(geometry) || $opts(layout)} { |
||||
set h [create_file $disk -createdisposition open_existing] |
||||
} |
||||
|
||||
trap { |
||||
if {$opts(all) || $opts(geometry)} { |
||||
# IOCTL_DISK_GET_DRIVE_GEOMETRY - 0x70000 |
||||
if {[binary scan [device_ioctl $h 0x70000 -outputcount 24] "wiiii" geom(-cylinders) geom(-mediatype) geom(-trackspercylinder) geom(-sectorspertrack) geom(-bytespersector)] != 5} { |
||||
error "DeviceIoControl 0x70000 on disk '$disk' returned insufficient data." |
||||
} |
||||
lappend result -geometry [array get geom] |
||||
} |
||||
|
||||
if {$opts(all) || $opts(layout)} { |
||||
# XP and later - IOCTL_DISK_GET_DRIVE_LAYOUT_EX |
||||
set data [device_ioctl $h 0x70050 -outputcount 624] |
||||
if {[binary scan $data "i i" partstyle layout(-partitioncount)] != 2} { |
||||
error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." |
||||
} |
||||
set layout(-partitionstyle) [_partition_style_sym $partstyle] |
||||
switch -exact -- $layout(-partitionstyle) { |
||||
mbr { |
||||
if {[binary scan $data "@8 i" layout(-signature)] != 1} { |
||||
error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." |
||||
} |
||||
} |
||||
gpt { |
||||
set pi(-diskid) [_binary_to_guid $data 32] |
||||
if {[binary scan $data "@8 w w i" layout(-startingusableoffset) layout(-usablelength) layout(-maxpartitioncount)] != 3} { |
||||
error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." |
||||
} |
||||
} |
||||
raw - |
||||
unknown { |
||||
# No fields to add |
||||
} |
||||
} |
||||
|
||||
set layout(-partitions) [list ] |
||||
for {set i 0} {$i < $layout(-partitioncount)} {incr i} { |
||||
# Decode each partition in turn. Sizeof of PARTITION_INFORMATION_EX is 144 |
||||
lappend layout(-partitions) [_decode_PARTITION_INFORMATION_EX_binary $data [expr {48 + (144*$i)}]] |
||||
} |
||||
lappend result -layout [array get layout] |
||||
} |
||||
|
||||
} finally { |
||||
if {[info exists h]} { |
||||
CloseHandle $h |
||||
} |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
# Given a Tcl binary and offset, decode the PARTITION_INFORMATION_EX record |
||||
proc twapi::_decode_PARTITION_INFORMATION_EX_binary {bin off} { |
||||
if {[binary scan $bin "@$off i x4 w w i c" \ |
||||
pi(-partitionstyle) \ |
||||
pi(-startingoffset) \ |
||||
pi(-partitionlength) \ |
||||
pi(-partitionnumber) \ |
||||
pi(-rewritepartition)] != 5} { |
||||
error "Truncated partition structure." |
||||
} |
||||
|
||||
set pi(-partitionstyle) [_partition_style_sym $pi(-partitionstyle)] |
||||
|
||||
# MBR/GPT are at offset 32 in the structure |
||||
switch -exact -- $pi(-partitionstyle) { |
||||
mbr { |
||||
if {[binary scan $bin "@$off x32 c c c x i" pi(-partitiontype) pi(-bootindicator) pi(-recognizedpartition) pi(-hiddensectors)] != 4} { |
||||
error "Truncated partition structure." |
||||
} |
||||
# Show partition type in hex, not negative number |
||||
set pi(-partitiontype) [format 0x%2.2x [expr {0xff & $pi(-partitiontype)}]] |
||||
} |
||||
gpt { |
||||
set pi(-partitiontype) [_binary_to_guid $bin [expr {$off+32}]] |
||||
set pi(-partitionif) [_binary_to_guid $bin [expr {$off+48}]] |
||||
if {[binary scan $bin "@$off x64 w" pi(-attributes)] != 1} { |
||||
error "Truncated partition structure." |
||||
} |
||||
set pi(-name) [_ucs16_binary_to_string [string range $bin [expr {$off+72}] end]] |
||||
} |
||||
raw - |
||||
unknown { |
||||
# No fields to add |
||||
} |
||||
|
||||
} |
||||
|
||||
return [array get pi] |
||||
} |
||||
|
||||
# IOCTL_STORAGE_EJECT_MEDIA |
||||
interp alias {} twapi::eject {} twapi::eject_media |
||||
proc twapi::eject_media device { |
||||
# http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q165721& |
||||
set h [_open_disk_device $device] |
||||
trap { |
||||
device_ioctl $h 0x90018; # FSCTL_LOCK_VOLUME |
||||
device_ioctl $h 0x90020; # FSCTL_DISMOUNT_VOLUME |
||||
# IOCTL_STORAGE_MEDIA_REMOVAL (0) |
||||
device_ioctl $h 0x2d4804 -input [_PREVENT_MEDIA_REMOVAL 0] |
||||
device_ioctl $h 0x2d4808; # IOCTL_STORAGE_EJECT_MEDIA |
||||
} finally { |
||||
close_handle $h |
||||
} |
||||
} |
||||
|
||||
# IOCTL_DISK_LOAD_MEDIA |
||||
# TBD - should we use IOCTL_DISK_LOAD_MEDIA2 instead (0x2d080c) see |
||||
# SDK, faster if read / write access not necessary. We are closing |
||||
# the handle right away anyway but would that stop other apps from |
||||
# acessing the file system on the CD ? Need to try (note device |
||||
# has to be opened with FILE_READ_ATTRIBUTES only in that case) |
||||
|
||||
interp alias {} twapi::load_media {} twapi::_issue_disk_ioctl 0x2d480c |
||||
|
||||
# FSCTL_LOCK_VOLUME |
||||
# TBD - interp alias {} twapi::lock_volume {} twapi::_issue_disk_ioctl 0x90018 |
||||
# FSCTL_LOCK_VOLUME |
||||
# TBD - interp alias {} twapi::unlock_volume {} twapi::_issue_disk_ioctl 0x9001c |
||||
|
||||
proc twapi::_lock_media {lock device} { |
||||
# IOCTL_STORAGE_MEDIA_REMOVAL |
||||
_issue_disk_ioctl 0x2d4804 $device -input [_PREVENT_MEDIA_REMOVAL $lock] |
||||
} |
||||
interp alias {} twapi::lock_media {} twapi::_lock_media 1 |
||||
interp alias {} twapi::unlock_media {} twapi::_lock_media 0 |
||||
|
||||
proc twapi::_issue_disk_ioctl {ioctl device args} { |
||||
set h [_open_disk_device $device] |
||||
trap { |
||||
device_ioctl $h $ioctl {*}$args |
||||
} finally { |
||||
close_handle $h |
||||
} |
||||
} |
||||
|
||||
twapi::proc* twapi::_open_disk_device {device} { |
||||
package require twapi_storage |
||||
} { |
||||
# device must be "cdrom", X:, X:\\, X:/, a volume or a physical disk as |
||||
# returned from find_physical_disks |
||||
switch -regexp -nocase -- $device { |
||||
{^cdrom$} { |
||||
foreach drive [find_logical_drives] { |
||||
if {![catch {get_drive_type $drive} drive_type]} { |
||||
if {$drive_type eq "cdrom"} { |
||||
set device "\\\\.\\$drive" |
||||
break |
||||
} |
||||
} |
||||
} |
||||
if {$device eq "cdrom"} { |
||||
error "Could not find a CD-ROM device." |
||||
} |
||||
} |
||||
{^[[:alpha:]]:(/|\\)?$} { |
||||
set device "\\\\.\\[string range $device 0 1]" |
||||
} |
||||
{^\\\\\?\\.*#\{[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}\}$} { |
||||
# Device name ok |
||||
} |
||||
{^\\\\\?\\Volume\{[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}\}\\?$} { |
||||
# Volume name ok. But make sure we trim off any trailing |
||||
# \ since create_file will open the root dir instead of the device |
||||
set device [string trimright $device \\] |
||||
} |
||||
default { |
||||
# Just to prevent us from opening some file instead |
||||
error "Invalid device name '$device'" |
||||
} |
||||
} |
||||
|
||||
# http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q165721& |
||||
return [create_file $device -access {generic_read generic_write} \ |
||||
-createdisposition open_existing \ |
||||
-share {read write}] |
||||
} |
||||
|
||||
|
||||
# Map a partition style code to a symbol |
||||
proc twapi::_partition_style_sym {partstyle} { |
||||
set partstyle [lindex {mbr gpt raw} $partstyle] |
||||
if {$partstyle ne ""} { |
||||
return $partstyle |
||||
} |
||||
return "unknown" |
||||
} |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,391 @@
|
||||
# |
||||
# Copyright (c) 2004-2012, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
package require registry |
||||
|
||||
namespace eval twapi { |
||||
# We maintain caches so we do not do lookups all the time |
||||
# TBD - have a means of clearing this out |
||||
variable _eventlog_message_cache |
||||
set _eventlog_message_cache {} |
||||
} |
||||
|
||||
|
||||
# Read the event log |
||||
proc twapi::eventlog_read {hevl args} { |
||||
_eventlog_valid_handle $hevl read raise |
||||
|
||||
array set opts [parseargs args { |
||||
seek.int |
||||
{direction.arg forward} |
||||
}] |
||||
|
||||
if {[info exists opts(seek)]} { |
||||
set flags 2; # Seek |
||||
set offset $opts(seek) |
||||
} else { |
||||
set flags 1; # Sequential read |
||||
set offset 0 |
||||
} |
||||
|
||||
switch -glob -- $opts(direction) { |
||||
"" - |
||||
forw* { |
||||
setbits flags 4 |
||||
} |
||||
back* { |
||||
setbits flags 8 |
||||
} |
||||
default { |
||||
error "Invalid value '$opts(direction)' for -direction option" |
||||
} |
||||
} |
||||
|
||||
set results [list ] |
||||
|
||||
trap { |
||||
set recs [ReadEventLog $hevl $flags $offset] |
||||
} onerror {TWAPI_WIN32 38} { |
||||
# EOF - no more |
||||
set recs [list ] |
||||
} |
||||
foreach event $recs { |
||||
dict set event -type [string map {0 success 1 error 2 warning 4 information 8 auditsuccess 16 auditfailure} [dict get $event -level]] |
||||
lappend results $event |
||||
} |
||||
|
||||
return $results |
||||
} |
||||
|
||||
|
||||
# Get the oldest event log record index. $hevl must be read handle |
||||
proc twapi::eventlog_oldest {hevl} { |
||||
_eventlog_valid_handle $hevl read raise |
||||
return [GetOldestEventLogRecord $hevl] |
||||
} |
||||
|
||||
# Get the event log record count. $hevl must be read handle |
||||
proc twapi::eventlog_count {hevl} { |
||||
_eventlog_valid_handle $hevl read raise |
||||
return [GetNumberOfEventLogRecords $hevl] |
||||
} |
||||
|
||||
# Check if the event log is full. $hevl may be either read or write handle |
||||
# (only win2k plus) |
||||
proc twapi::eventlog_is_full {hevl} { |
||||
# Does not matter if $hevl is read or write, but verify it is a handle |
||||
_eventlog_valid_handle $hevl read |
||||
return [Twapi_IsEventLogFull $hevl] |
||||
} |
||||
|
||||
# Backup the event log |
||||
proc twapi::eventlog_backup {hevl file} { |
||||
_eventlog_valid_handle $hevl read raise |
||||
BackupEventLog $hevl $file |
||||
} |
||||
|
||||
# Clear the event log |
||||
proc twapi::eventlog_clear {hevl args} { |
||||
_eventlog_valid_handle $hevl read raise |
||||
array set opts [parseargs args {backup.arg} -nulldefault] |
||||
ClearEventLog $hevl $opts(backup) |
||||
} |
||||
|
||||
|
||||
# Formats the given event log record message |
||||
# |
||||
proc twapi::eventlog_format_message {rec args} { |
||||
variable _eventlog_message_cache |
||||
|
||||
array set opts [parseargs args { |
||||
width.int |
||||
langid.int |
||||
} -nulldefault] |
||||
|
||||
set source [dict get $rec -source] |
||||
set eventid [dict get $rec -eventid] |
||||
|
||||
if {[dict exists $_eventlog_message_cache $source fmtstring $opts(langid) $eventid]} { |
||||
set fmtstring [dict get $_eventlog_message_cache $source fmtstring $opts(langid) $eventid] |
||||
dict incr _eventlog_message_cache __fmtstring_hits |
||||
} else { |
||||
dict incr _eventlog_message_cache __fmtstring_misses |
||||
|
||||
# Find the registry key if we do not have it already |
||||
if {[dict exists $_eventlog_message_cache $source regkey]} { |
||||
dict incr _eventlog_message_cache __regkey_hits |
||||
set regkey [dict get $_eventlog_message_cache $source regkey] |
||||
} else { |
||||
set regkey [_find_eventlog_regkey $source] |
||||
dict set _eventlog_message_cache $source regkey $regkey |
||||
dict incr _eventlog_message_cache __regkey_misses |
||||
} |
||||
|
||||
# Get the message file, if there is one |
||||
if {! [catch {registry get $regkey "EventMessageFile"} path]} { |
||||
# Try each file listed in turn |
||||
foreach dll [split $path \;] { |
||||
set dll [expand_environment_strings $dll] |
||||
if {! [catch { |
||||
set fmtstring [format_message -module $dll -messageid $eventid -width $opts(width) -langid $opts(langid)] |
||||
} msg]} { |
||||
dict set _eventlog_message_cache $source fmtstring $opts(langid) $eventid $fmtstring |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if {! [info exists fmtstring]} { |
||||
dict incr _eventlog_message_cache __notfound |
||||
|
||||
set fmt "The message file or event definition for event id [dict get $rec -eventid] from source [dict get $rec -source] was not found. The following information was part of the event: " |
||||
set flds [list ] |
||||
for {set i 1} {$i <= [llength [dict get $rec -params]]} {incr i} { |
||||
lappend flds %$i |
||||
} |
||||
append fmt [join $flds ", "] |
||||
return [format_message -fmtstring $fmt \ |
||||
-params [dict get $rec -params] -width $opts(width)] |
||||
} |
||||
|
||||
set msg [format_message -fmtstring $fmtstring -params [dict get $rec -params]] |
||||
|
||||
# We'd found a message from the message file and replaced the string |
||||
# parameters. Now fill in the parameter file values if any. Note these are |
||||
# separate from the string parameters passed in through rec(-params) |
||||
|
||||
# First check if the formatted string itself still has placeholders |
||||
# Place holder for the parameters file are supposed to start |
||||
# with two % chars. Unfortunately, not all apps, even Microsoft's own |
||||
# DCOM obey this. So check for both % and %% |
||||
set placeholder_indices [regexp -indices -all -inline {%?%\d+} $msg] |
||||
if {[llength $placeholder_indices] == 0} { |
||||
# No placeholders. |
||||
return $msg |
||||
} |
||||
|
||||
# Loop through to replace placeholders. |
||||
set msg2 ""; # Holds result after param replacement |
||||
set prev_end 0 |
||||
foreach placeholder $placeholder_indices { |
||||
lassign $placeholder start end |
||||
# Append the stuff between previous placeholder and this one |
||||
append msg2 [string range $msg $prev_end [expr {$start-1}]] |
||||
set repl [string range $msg $start $end]; # Default if not found |
||||
set paramid [string trimleft $repl %]; # Skip "%" |
||||
if {[dict exists $_eventlog_message_cache $source paramstring $opts(langid) $paramid]} { |
||||
dict incr _eventlog_message_cache __paramstring_hits |
||||
set repl [format_message -fmtstring [dict get $_eventlog_message_cache $source paramstring $opts(langid) $paramid] -params [dict get $rec -params]] |
||||
} else { |
||||
dict incr _eventlog_message_cache __paramstring_misses |
||||
# Not in cache, need to look up |
||||
if {![info exists paramfiles]} { |
||||
# Construct list of parameter string files |
||||
|
||||
# TBD - cache registry key results? |
||||
# Find the registry key if we do not have it already |
||||
if {![info exists regkey]} { |
||||
if {[dict exists $_eventlog_message_cache $source regkey]} { |
||||
dict incr _eventlog_message_cache __regkey_hits |
||||
set regkey [dict get $_eventlog_message_cache $source regkey] |
||||
} else { |
||||
dict incr _eventlog_message_cache __regkey_misses |
||||
set regkey [_find_eventlog_regkey $source] |
||||
dict set _eventlog_message_cache $source regkey $regkey |
||||
} |
||||
} |
||||
set paramfiles {} |
||||
if {! [catch {registry get $regkey "ParameterMessageFile"} path]} { |
||||
# Loop through every placeholder, look for the entry in the |
||||
# parameters file and replace it if found |
||||
foreach paramfile [split $path \;] { |
||||
lappend paramfiles [expand_environment_strings $paramfile] |
||||
} |
||||
} |
||||
} |
||||
# Try each file listed in turn |
||||
foreach paramfile $paramfiles { |
||||
if {! [catch { |
||||
set paramstring [string trimright [format_message -module $paramfile -messageid $paramid -langid $opts(langid)] \r\n] |
||||
} ]} { |
||||
# Found the replacement |
||||
dict set _eventlog_message_cache $source paramstring $opts(langid) $paramid $paramstring |
||||
set repl [format_message -fmtstring $paramstring -params [dict get $rec -params]] |
||||
break |
||||
} |
||||
} |
||||
} |
||||
append msg2 $repl |
||||
set prev_end [incr end] |
||||
} |
||||
|
||||
# Tack on tail after last placeholder |
||||
append msg2 [string range $msg $prev_end end] |
||||
return $msg2 |
||||
} |
||||
|
||||
# Format the category |
||||
proc twapi::eventlog_format_category {rec args} { |
||||
|
||||
array set opts [parseargs args { |
||||
width.int |
||||
langid.int |
||||
} -nulldefault] |
||||
|
||||
set category [dict get $rec -category] |
||||
if {$category == 0} { |
||||
return "" |
||||
} |
||||
|
||||
variable _eventlog_message_cache |
||||
|
||||
set source [dict get $rec -source] |
||||
|
||||
# Get the category string from cache, if there is one |
||||
if {[dict exists $_eventlog_message_cache $source category $opts(langid) $category]} { |
||||
dict incr _eventlog_message_cache __category_hits |
||||
set fmtstring [dict get $_eventlog_message_cache $source category $opts(langid) $category] |
||||
} else { |
||||
dict incr _eventlog_message_cache __category_misses |
||||
|
||||
# Find the registry key if we do not have it already |
||||
if {[dict exists $_eventlog_message_cache $source regkey]} { |
||||
dict incr _eventlog_message_cache __regkey_hits |
||||
set regkey [dict get $_eventlog_message_cache $source regkey] |
||||
} else { |
||||
set regkey [_find_eventlog_regkey $source] |
||||
dict set _eventlog_message_cache $source regkey $regkey |
||||
dict incr _eventlog_message_cache __regkey_misses |
||||
} |
||||
|
||||
if {! [catch {registry get $regkey "CategoryMessageFile"} path]} { |
||||
# Try each file listed in turn |
||||
foreach dll [split $path \;] { |
||||
set dll [expand_environment_strings $dll] |
||||
if {! [catch { |
||||
set fmtstring [format_message -module $dll -messageid $category -width $opts(width) -langid $opts(langid)] |
||||
} msg]} { |
||||
dict set _eventlog_message_cache $source category $opts(langid) $category $fmtstring |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if {![info exists fmtstring]} { |
||||
set fmtstring "Category $category" |
||||
dict set _eventlog_message_cache $source category $opts(langid) $category $fmtstring |
||||
} |
||||
|
||||
return [format_message -fmtstring $fmtstring -params [dict get $rec -params]] |
||||
} |
||||
|
||||
proc twapi::eventlog_monitor_start {hevl script} { |
||||
variable _eventlog_notification_scripts |
||||
|
||||
set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] |
||||
if {[catch {NotifyChangeEventLog $hevl $hevent} msg]} { |
||||
CloseHandle $hevent |
||||
error $msg $::errorInfo $::errorCode |
||||
} |
||||
|
||||
wait_on_handle $hevent -async twapi::_eventlog_notification_handler |
||||
set _eventlog_notification_scripts($hevent) $script |
||||
|
||||
# We do not want the application mistakenly closing the event |
||||
# while being waited on by the thread pool. That would be a big NO-NO |
||||
# so change the handle type so it cannot be passed to close_handle. |
||||
return [list evl $hevent] |
||||
} |
||||
|
||||
# Stop any notifications. Note these will stop even if the event log |
||||
# handle is closed but leave the event dangling. |
||||
proc twapi::eventlog_monitor_stop {hevent} { |
||||
variable _eventlog_notification_scripts |
||||
set hevent [lindex $hevent 1] |
||||
if {[info exists _eventlog_notification_scripts($hevent)]} { |
||||
unset _eventlog_notification_scripts($hevent) |
||||
cancel_wait_on_handle $hevent |
||||
CloseHandle $hevent |
||||
} |
||||
} |
||||
|
||||
proc twapi::_eventlog_notification_handler {hevent event} { |
||||
variable _eventlog_notification_scripts |
||||
if {[info exists _eventlog_notification_scripts($hevent)] && |
||||
$event eq "signalled"} { |
||||
uplevel #0 $_eventlog_notification_scripts($hevent) [list [list evl $hevent]] |
||||
} |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::eventlog_subscribe {source} { |
||||
set hevl [eventlog_open -source $source] |
||||
set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] |
||||
if {[catch {NotifyChangeEventLog $hevl $hevent} msg]} { |
||||
set erinfo $::errorInfo |
||||
set ercode $::errorCode |
||||
CloseHandle $hevent |
||||
error $hsubscribe $erinfo $ercode |
||||
} |
||||
|
||||
return [list $hevl $hevent] |
||||
} |
||||
|
||||
# Utility procs |
||||
|
||||
# Find the registry key corresponding the given event log source |
||||
proc twapi::_find_eventlog_regkey {source} { |
||||
set topkey {HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog} |
||||
|
||||
# Set a default list of children to work around an issue in |
||||
# the Tcl [registry keys] command where a ERROR_MORE_DATA is returned |
||||
# instead of a retry with a larger buffer. |
||||
set keys {Application Security System} |
||||
catch {set keys [registry keys $topkey]} |
||||
# Get all keys under this key and look for a source under that |
||||
foreach key $keys { |
||||
# See above Tcl issue |
||||
set srckeys {} |
||||
catch {set srckeys [registry keys "${topkey}\\$key"]} |
||||
foreach srckey $srckeys { |
||||
if {[string equal -nocase $srckey $source]} { |
||||
return "${topkey}\\${key}\\$srckey" |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Default to Application - TBD |
||||
return "${topkey}\\Application" |
||||
} |
||||
|
||||
proc twapi::_eventlog_dump {source chan} { |
||||
set hevl [eventlog_open -source $source] |
||||
while {[llength [set events [eventlog_read $hevl]]]} { |
||||
# print out each record |
||||
foreach eventrec $events { |
||||
array set event $eventrec |
||||
set timestamp [clock format $event(-timewritten) -format "%x %X"] |
||||
set source $event(-source) |
||||
set category [twapi::eventlog_format_category $eventrec -width -1] |
||||
set message [twapi::eventlog_format_message $eventrec -width -1] |
||||
puts $chan "$timestamp $source $category $message" |
||||
} |
||||
} |
||||
eventlog_close $hevl |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
# If we are not being sourced from a executable resource, need to |
||||
# source the remaining support files. In the former case, they are |
||||
# automatically combined into one so the sourcing is not needed. |
||||
if {![info exists twapi::twapi_eventlog_rc_sourced]} { |
||||
source [file join [file dirname [info script]] evt.tcl] |
||||
source [file join [file dirname [info script]] winlog.tcl] |
||||
} |
@ -0,0 +1,718 @@
|
||||
# |
||||
# Copyright (c) 2012-2014, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Event log handling for Vista and later |
||||
|
||||
namespace eval twapi { |
||||
variable _evt; # See _evt_init |
||||
|
||||
# System event fields in order returned by _evt_decode_event_system_fields |
||||
twapi::record evt_system_fields { |
||||
-providername -providerguid -eventid -qualifiers -level -task |
||||
-opcode -keywordmask -timecreated -eventrecordid -activityid |
||||
-relatedactivityid -pid -tid -channel |
||||
-computer -sid -version |
||||
} |
||||
|
||||
proc _evt_init {} { |
||||
variable _evt |
||||
|
||||
# Various structures that we maintain / cache for efficiency as they |
||||
# are commonly used are kept in the _evt array with the following keys: |
||||
|
||||
# system_render_context_handle - is the handle to a rendering |
||||
# context for the system portion of an event |
||||
set _evt(system_render_context_handle) [evt_render_context_system] |
||||
|
||||
# user_render_context_handle - is the handle to a rendering |
||||
# context for the user data portion of an event |
||||
set _evt(user_render_context_handle) [evt_render_context_user] |
||||
|
||||
# render_buffer - is NULL or holds a pointer to the buffer used to |
||||
# retrieve values so does not have to be reallocated every time. |
||||
set _evt(render_buffer) NULL |
||||
|
||||
# publisher_handles - caches publisher names to their meta information. |
||||
# This is a dictionary indexed with nested keys - |
||||
# publisher, session, lcid. TBD - need a mechanism to clear ? |
||||
set _evt(publisher_handles) [dict create] |
||||
|
||||
# -levelname - dict of publisher name / level number to level names |
||||
set _evt(-levelname) {} |
||||
|
||||
# -taskname - dict of publisher name / task number to task name |
||||
set _evt(-taskname) {} |
||||
|
||||
# -opcodename - dict of publisher name / opcode number to opcode name |
||||
set _evt(-opcodename) {} |
||||
|
||||
# No-op the proc once init is done |
||||
proc _evt_init {} {} |
||||
} |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_local_session {} { |
||||
return NULL |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_local_session? {hsess} { |
||||
return [pointer_null? $hsess] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_open_session {server args} { |
||||
array set opts [parseargs args { |
||||
user.arg |
||||
domain.arg |
||||
password.arg |
||||
{authtype.arg 0} |
||||
} -nulldefault -maxleftover 0] |
||||
|
||||
if {![string is integer -strict $opts(authtype)]} { |
||||
set opts(authtype) [dict get {default 0 negotiate 1 kerberos 2 ntlm 3} [string tolower $opts(authtype)]] |
||||
} |
||||
|
||||
return [EvtOpenSession 1 [list $server $opts(user) $opts(domain) $opts(password) $opts(authtype)] 0 0] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_close_session {hsess} { |
||||
if {![evt_local_session? $hsess]} { |
||||
evt_close $hsess |
||||
} |
||||
} |
||||
|
||||
proc twapi::evt_channels {{hevtsess NULL}} { |
||||
# TBD - document hevtsess |
||||
set chnames {} |
||||
set hevt [EvtOpenChannelEnum $hevtsess 0] |
||||
trap { |
||||
while {[set chname [EvtNextChannelPath $hevt]] ne ""} { |
||||
lappend chnames $chname |
||||
} |
||||
} finally { |
||||
evt_close $hevt |
||||
} |
||||
|
||||
return $chnames |
||||
} |
||||
|
||||
proc twapi::evt_clear_log {chanpath args} { |
||||
# TBD - document -session |
||||
array set opts [parseargs args { |
||||
{session.arg NULL} |
||||
{backup.arg ""} |
||||
} -maxleftover 0] |
||||
|
||||
return [EvtClearLog $opts(session) $chanpath [_evt_normalize_path $opts(backup)] 0] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_archive_exported_log {logpath args} { |
||||
array set opts [parseargs args { |
||||
{session.arg NULL} |
||||
{lcid.int 0} |
||||
} -maxleftover 0] |
||||
|
||||
return [EvtArchiveExportedLog $opts(session) [_evt_normalize_path $logpath] $opts(lcid) 0] |
||||
} |
||||
|
||||
proc twapi::evt_export_log {outfile args} { |
||||
# TBD - document -session |
||||
array set opts [parseargs args { |
||||
{session.arg NULL} |
||||
file.arg |
||||
channel.arg |
||||
{query.arg *} |
||||
{ignorequeryerrors 0 0x1000} |
||||
} -maxleftover 0] |
||||
|
||||
if {([info exists opts(file)] && [info exists opts(channel)]) || |
||||
! ([info exists opts(file)] || [info exists opts(channel)])} { |
||||
error "Exactly one of -file or -channel must be specified." |
||||
} |
||||
|
||||
if {[info exists opts(file)]} { |
||||
set path [_evt_normalize_path $opts(file)] |
||||
incr opts(ignorequeryerrors) 2 |
||||
} else { |
||||
set path $opts(channel) |
||||
incr opts(ignorequeryerrors) 1 |
||||
} |
||||
|
||||
return [EvtExportLog $opts(session) $path $opts(query) [_evt_normalize_path $outfile] $opts(ignorequeryerrors)] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_create_bookmark {{mark ""}} { |
||||
return [EvtCreateBookmark $mark] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_render_context_xpaths {xpaths} { |
||||
return [EvtCreateRenderContext $xpaths 0] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_render_context_system {} { |
||||
return [EvtCreateRenderContext {} 1] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_render_context_user {} { |
||||
return [EvtCreateRenderContext {} 2] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_open_channel_config {chanpath args} { |
||||
array set opts [parseargs args { |
||||
{session.arg NULL} |
||||
} -maxleftover 0] |
||||
|
||||
return [EvtOpenChannelConfig $opts(session) $chanpath 0] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_get_channel_config {hevt args} { |
||||
set result {} |
||||
foreach opt $args { |
||||
lappend result $opt \ |
||||
[EvtGetChannelConfigProperty $hevt \ |
||||
[_evt_map_channel_config_property $hevt $propid]] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_set_channel_config {hevt propid val} { |
||||
return [EvtSetChannelConfigProperty $hevt [_evt_map_channel_config_property $propid 0 $val]] |
||||
} |
||||
|
||||
|
||||
# TBD - document |
||||
proc twapi::_evt_map_channel_config_property {propid} { |
||||
if {[string is integer -strict $propid]} { |
||||
return $propid |
||||
} |
||||
|
||||
# Note: values are from winevt.h, Win7 SDK has typos for last few |
||||
return [dict get { |
||||
-enabled 0 |
||||
-isolation 1 |
||||
-type 2 |
||||
-owningpublisher 3 |
||||
-classiceventlog 4 |
||||
-access 5 |
||||
-loggingretention 6 |
||||
-loggingautobackup 7 |
||||
-loggingmaxsize 8 |
||||
-logginglogfilepath 9 |
||||
-publishinglevel 10 |
||||
-publishingkeywords 11 |
||||
-publishingcontrolguid 12 |
||||
-publishingbuffersize 13 |
||||
-publishingminbuffers 14 |
||||
-publishingmaxbuffers 15 |
||||
-publishinglatency 16 |
||||
-publishingclocktype 17 |
||||
-publishingsidtype 18 |
||||
-publisherlist 19 |
||||
-publishingfilemax 20 |
||||
} $propid] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_event_info {hevt args} { |
||||
set result {} |
||||
foreach opt $args { |
||||
lappend result $opt [EvtGetEventInfo $hevt \ |
||||
[dict get {-queryids 0 -path 1} $opt]] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
|
||||
# TBD - document |
||||
proc twapi::evt_event_metadata_property {hevt args} { |
||||
set result {} |
||||
foreach opt $args { |
||||
lappend result $opt \ |
||||
[EvtGetEventMetadataProperty $hevt \ |
||||
[dict get { |
||||
-id 0 -version 1 -channel 2 -level 3 |
||||
-opcode 4 -task 5 -keyword 6 -messageid 7 -template 8 |
||||
} $opt]] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
|
||||
# TBD - document |
||||
proc twapi::evt_open_log_info {args} { |
||||
array set opts [parseargs args { |
||||
{session.arg NULL} |
||||
file.arg |
||||
channel.arg |
||||
} -maxleftover 0] |
||||
|
||||
if {([info exists opts(file)] && [info exists opts(channel)]) || |
||||
! ([info exists opts(file)] || [info exists opts(channel)])} { |
||||
error "Exactly one of -file or -channel must be specified." |
||||
} |
||||
|
||||
if {[info exists opts(file)]} { |
||||
set path [_evt_normalize_path $opts(file)] |
||||
set flags 0x2 |
||||
} else { |
||||
set path $opts(channel) |
||||
set flags 0x1 |
||||
} |
||||
|
||||
return [EvtOpenLog $opts(session) $path $flags] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_log_info {hevt args} { |
||||
set result {} |
||||
foreach opt $args { |
||||
lappend result $opt [EvtGetLogInfo $hevt [dict get { |
||||
-creationtime 0 -lastaccesstime 1 -lastwritetime 2 |
||||
-filesize 3 -attributes 4 -numberoflogrecords 5 |
||||
-oldestrecordnumber 6 -full 7 |
||||
} $opt]] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_publisher_metadata_property {hpub args} { |
||||
set result {} |
||||
foreach opt $args { |
||||
set val [EvtGetPublisherMetadataProperty $hpub [dict get { |
||||
-publisherguid 0 -resourcefilepath 1 -parameterfilepath 2 |
||||
-messagefilepath 3 -helplink 4 -publishermessageid 5 |
||||
-channelreferences 6 -levels 12 -tasks 16 |
||||
-opcodes 21 -keywords 25 |
||||
} $opt] 0] |
||||
if {$opt ni {-channelreferences -levels -tasks -opcodes -keywords}} { |
||||
lappend result $opt $val |
||||
continue |
||||
} |
||||
set n [EvtGetObjectArraySize $val] |
||||
set val2 {} |
||||
for {set i 0} {$i < $n} {incr i} { |
||||
set rec {} |
||||
foreach {opt2 iopt} [dict get { |
||||
-channelreferences { -channelreferencepath 7 |
||||
-channelreferenceindex 8 -channelreferenceid 9 |
||||
-channelreferenceflags 10 -channelreferencemessageid 11} |
||||
-levels { -levelname 13 -levelvalue 14 -levelmessageid 15 } |
||||
-tasks { -taskname 17 -taskeventguid 18 -taskvalue 19 |
||||
-taskmessageid 20} |
||||
-opcodes {-opcodename 22 -opcodevalue 23 -opcodemessageid 24} |
||||
-keywords {-keywordname 26 -keywordvalue 27 |
||||
-keywordmessageid 28} |
||||
} $opt] { |
||||
lappend rec $opt2 [EvtGetObjectArrayProperty $val $iopt $i] |
||||
} |
||||
lappend val2 $rec |
||||
} |
||||
|
||||
evt_close $val |
||||
lappend result $opt $val2 |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_query_info {hq args} { |
||||
set result {} |
||||
foreach opt $args { |
||||
lappend result $opt [EvtGetQueryInfo $hq [dict get { |
||||
-names 1 statuses 2 |
||||
} $opt]] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_object_array_size {hevt} { |
||||
return [EvtGetObjectArraySize $hevt] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_object_array_property {hevt index args} { |
||||
set result {} |
||||
|
||||
foreach opt $args { |
||||
lappend result $opt \ |
||||
[EvtGetObjectArrayProperty $hevt [dict get { |
||||
-channelreferencepath 7 |
||||
-channelreferenceindex 8 -channelreferenceid 9 |
||||
-channelreferenceflags 10 -channelreferencemessageid 11 |
||||
-levelname 13 -levelvalue 14 -levelmessageid 15 |
||||
-taskname 17 -taskeventguid 18 -taskvalue 19 |
||||
-taskmessageid 20 -opcodename 22 |
||||
-opcodevalue 23 -opcodemessageid 24 |
||||
-keywordname 26 -keywordvalue 27 -keywordmessageid 28 |
||||
}] $index] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
proc twapi::evt_publishers {{hsess NULL}} { |
||||
set pubs {} |
||||
set hevt [EvtOpenPublisherEnum $hsess 0] |
||||
trap { |
||||
while {[set pub [EvtNextPublisherId $hevt]] ne ""} { |
||||
lappend pubs $pub |
||||
} |
||||
} finally { |
||||
evt_close $hevt |
||||
} |
||||
|
||||
return $pubs |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_open_publisher_metadata {pub args} { |
||||
array set opts [parseargs args { |
||||
{session.arg NULL} |
||||
logfile.arg |
||||
lcid.int |
||||
} -nulldefault -maxleftover 0] |
||||
|
||||
return [EvtOpenPublisherMetadata $opts(session) $pub $opts(logfile) $opts(lcid) 0] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_publisher_events_metadata {hpub args} { |
||||
set henum [EvtOpenEventMetadataEnum $hpub] |
||||
|
||||
# It is faster to build a list and then have Tcl shimmer to a dict when |
||||
# required |
||||
set meta {} |
||||
trap { |
||||
while {[set hmeta [EvtNextEventMetadata $henum 0]] ne ""} { |
||||
lappend meta [evt_event_metadata_property $hmeta {*}$args] |
||||
evt_close $hmeta |
||||
} |
||||
} finally { |
||||
evt_close $henum |
||||
} |
||||
|
||||
return $meta |
||||
} |
||||
|
||||
proc twapi::evt_query {args} { |
||||
array set opts [parseargs args { |
||||
{session.arg NULL} |
||||
file.arg |
||||
channel.arg |
||||
{query.arg *} |
||||
{ignorequeryerrors 0 0x1000} |
||||
{direction.sym forward {forward 0x100 reverse 0x200 backward 0x200}} |
||||
} -maxleftover 0] |
||||
|
||||
if {([info exists opts(file)] && [info exists opts(channel)]) || |
||||
! ([info exists opts(file)] || [info exists opts(channel)])} { |
||||
error "Exactly one of -file or -channel must be specified." |
||||
} |
||||
|
||||
set flags $opts(ignorequeryerrors) |
||||
incr flags $opts(direction) |
||||
|
||||
if {[info exists opts(file)]} { |
||||
set path [_evt_normalize_path $opts(file)] |
||||
incr flags 0x2 |
||||
} else { |
||||
set path $opts(channel) |
||||
incr flags 0x1 |
||||
} |
||||
|
||||
return [EvtQuery $opts(session) $path $opts(query) $flags] |
||||
} |
||||
|
||||
proc twapi::evt_next {hresultset args} { |
||||
array set opts [parseargs args { |
||||
{timeout.int -1} |
||||
{count.int 1} |
||||
{status.arg} |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(status)]} { |
||||
upvar 1 $opts(status) status |
||||
return [EvtNext $hresultset $opts(count) $opts(timeout) 0 status] |
||||
} else { |
||||
return [EvtNext $hresultset $opts(count) $opts(timeout) 0] |
||||
} |
||||
} |
||||
|
||||
twapi::proc* twapi::_evt_decode_event_system_fields {hevt} { |
||||
_evt_init |
||||
} { |
||||
variable _evt |
||||
set _evt(render_buffer) [Twapi_EvtRenderValues $_evt(system_render_context_handle) $hevt $_evt(render_buffer)] |
||||
set rec [Twapi_ExtractEVT_RENDER_VALUES $_evt(render_buffer)] |
||||
return [evt_system_fields set $rec \ |
||||
-providername [atomize [evt_system_fields -providername $rec]] \ |
||||
-providerguid [atomize [evt_system_fields -providerguid $rec]] \ |
||||
-channel [atomize [evt_system_fields -channel $rec]] \ |
||||
-computer [atomize [evt_system_fields -computer $rec]]] |
||||
} |
||||
|
||||
# TBD - document. Returns a list of user data values |
||||
twapi::proc* twapi::evt_decode_event_userdata {hevt} { |
||||
_evt_init |
||||
} { |
||||
variable _evt |
||||
set _evt(render_buffer) [Twapi_EvtRenderValues $_evt(user_render_context_handle) $hevt $_evt(render_buffer)] |
||||
return [Twapi_ExtractEVT_RENDER_VALUES $_evt(render_buffer)] |
||||
} |
||||
|
||||
twapi::proc* twapi::evt_decode_events {hevts args} { |
||||
_evt_init |
||||
} { |
||||
variable _evt |
||||
|
||||
array set opts [parseargs args { |
||||
{values.arg NULL} |
||||
{session.arg NULL} |
||||
{logfile.arg ""} |
||||
{lcid.int 0} |
||||
ignorestring.arg |
||||
message |
||||
levelname |
||||
taskname |
||||
opcodename |
||||
keywords |
||||
xml |
||||
} -ignoreunknown -hyphenated] |
||||
|
||||
# SAME ORDER AS _evt_decode_event_system_fields |
||||
set decoded_fields [evt_system_fields] |
||||
set decoded_events {} |
||||
|
||||
# ORDER MUST BE SAME AS order in which values are appended below |
||||
foreach opt {-levelname -taskname -opcodename -keywords -xml -message} { |
||||
if {$opts($opt)} { |
||||
lappend decoded_fields $opt |
||||
} |
||||
} |
||||
|
||||
foreach hevt $hevts { |
||||
set decoded [_evt_decode_event_system_fields $hevt] |
||||
# Get publisher from hevt |
||||
set publisher [evt_system_fields -providername $decoded] |
||||
|
||||
if {! [dict exists $_evt(publisher_handles) $publisher $opts(-session) $opts(-lcid)]} { |
||||
if {[catch { |
||||
dict set _evt(publisher_handles) $publisher $opts(-session) $opts(-lcid) [EvtOpenPublisherMetadata $opts(-session) $publisher $opts(-logfile) $opts(-lcid) 0] |
||||
}]} { |
||||
# TBD - debug log |
||||
dict set _evt(publisher_handles) $publisher $opts(-session) $opts(-lcid) NULL |
||||
} |
||||
} |
||||
set hpub [dict get $_evt(publisher_handles) $publisher $opts(-session) $opts(-lcid)] |
||||
|
||||
# See if cached values are present for -levelname -taskname |
||||
# and -opcodename. TBD - can -keywords be added to this ? |
||||
foreach {intopt opt callflag} {-level -levelname 2 -task -taskname 3 -opcode -opcodename 4} { |
||||
if {$opts($opt)} { |
||||
set ival [evt_system_fields $intopt $decoded] |
||||
if {[dict exists $_evt($opt) $publisher $ival]} { |
||||
lappend decoded [dict get $_evt($opt) $publisher $ival] |
||||
} else { |
||||
# Not cached. Look it up. Value of 0 -> null so |
||||
# just use ignorestring if specified. |
||||
if {$ival == 0 && [info exists opts(-ignorestring)]} { |
||||
set optval $opts(-ignorestring) |
||||
} else { |
||||
if {[info exists opts(-ignorestring)]} { |
||||
if {[EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag optval]} { |
||||
dict set _evt($opt) $publisher $ival $optval |
||||
} else { |
||||
# Note result not cached if not found since |
||||
# ignorestring may be different on every call |
||||
set optval $opts(-ignorestring) |
||||
} |
||||
} else { |
||||
# -ignorestring not specified so |
||||
# will raise error if not found |
||||
set optval [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag] |
||||
dict set _evt($opt) $publisher $ival [atomize $optval] |
||||
} |
||||
} |
||||
lappend decoded $optval |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Non-cached fields |
||||
# ORDER MUST BE SAME AS decoded_fields ABOVE |
||||
foreach {opt callflag} { |
||||
-keywords 5 |
||||
-xml 9 |
||||
} { |
||||
if {$opts($opt)} { |
||||
if {[info exists opts(-ignorestring)]} { |
||||
if {! [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag optval]} { |
||||
set optval $opts(-ignorestring) |
||||
} |
||||
} else { |
||||
set optval [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag] |
||||
} |
||||
lappend decoded $optval |
||||
} |
||||
} |
||||
|
||||
# We treat -message differently because on failure we want |
||||
# to extract the user data. -ignorestring is not used for this |
||||
# unless user data extraction also fails |
||||
if {$opts(-message)} { |
||||
if {[EvtFormatMessage $hpub $hevt 0 $opts(-values) 1 message]} { |
||||
lappend decoded $message |
||||
} else { |
||||
# TBD - make sure we have a test for this case. |
||||
# TBD - log |
||||
if {[catch { |
||||
lappend decoded "Message for event could not be found. Event contained user data: [join [evt_decode_event_userdata $hevt] ,]" |
||||
} message]} { |
||||
if {[info exists opts(-ignorestring)]} { |
||||
lappend decoded $opts(-ignorestring) |
||||
} else { |
||||
error $message |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
lappend decoded_events $decoded |
||||
} |
||||
|
||||
return [list $decoded_fields $decoded_events] |
||||
} |
||||
|
||||
proc twapi::evt_decode_event {hevt args} { |
||||
return [recordarray index [evt_decode_events [list $hevt] {*}$args] 0 -format dict] |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_format_publisher_message {hpub msgid args} { |
||||
|
||||
array set opts [parseargs args { |
||||
{values.arg NULL} |
||||
} -maxleftover 0] |
||||
|
||||
return [EvtFormatMessage $hpub NULL $msgid $opts(values) 8] |
||||
} |
||||
|
||||
# TBD - document |
||||
# Where is this used? |
||||
proc twapi::evt_free_EVT_VARIANT_ARRAY {p} { |
||||
evt_free $p |
||||
} |
||||
|
||||
# TBD - document |
||||
# Where is this used? |
||||
proc twapi::evt_free_EVT_RENDER_VALUES {p} { |
||||
evt_free $p |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::evt_seek {hresults pos args} { |
||||
array set opts [parseargs args { |
||||
{origin.arg first {first last current}} |
||||
bookmark.arg |
||||
{strict 0 0x10000} |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(bookmark)]} { |
||||
set flags 4 |
||||
} else { |
||||
set flags [lsearch -exact {first last current} $opts(origin)] |
||||
incr flags; # 1 -> first, 2 -> last, 3 -> current |
||||
set opts(bookmark) NULL |
||||
} |
||||
|
||||
incr flags $opts(strict) |
||||
|
||||
EvtSeek $hresults $pos $opts(bookmark) 0 $flags |
||||
} |
||||
|
||||
proc twapi::evt_subscribe {path args} { |
||||
# TBD - document -session and -bookmark and -strict |
||||
array set opts [parseargs args { |
||||
{session.arg NULL} |
||||
{query.arg *} |
||||
bookmark.arg |
||||
includeexisting |
||||
{ignorequeryerrors 0 0x1000} |
||||
{strict 0 0x10000} |
||||
} -maxleftover 0] |
||||
|
||||
set flags [expr {$opts(ignorequeryerrors) | $opts(strict)}] |
||||
if {[info exists opts(bookmark)]} { |
||||
set flags [expr {$flags | 3}] |
||||
set bookmark $opts(origin) |
||||
} else { |
||||
set bookmark NULL |
||||
if {$opts(includeexisting)} { |
||||
set flags [expr {$flags | 2}] |
||||
} else { |
||||
set flags [expr {$flags | 1}] |
||||
} |
||||
} |
||||
|
||||
set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] |
||||
if {[catch { |
||||
EvtSubscribe $opts(session) $hevent $path $opts(query) $bookmark $flags |
||||
} hsubscribe]} { |
||||
set erinfo $::errorInfo |
||||
set ercode $::errorCode |
||||
CloseHandle $hevent |
||||
error $hsubscribe $erinfo $ercode |
||||
} |
||||
|
||||
return [list $hsubscribe $hevent] |
||||
} |
||||
|
||||
proc twapi::_evt_normalize_path {path} { |
||||
# Do not want to rely on [file normalize] returning "" for "" |
||||
if {$path eq ""} { |
||||
return "" |
||||
} else { |
||||
return [file nativename [file normalize $path]] |
||||
} |
||||
} |
||||
|
||||
proc twapi::_evt_dump {args} { |
||||
array set opts [parseargs args { |
||||
{outfd.arg stdout} |
||||
count.int |
||||
} -ignoreunknown] |
||||
|
||||
set hq [evt_query {*}$args] |
||||
trap { |
||||
while {[llength [set hevts [evt_next $hq]]]} { |
||||
trap { |
||||
foreach ev [recordarray getlist [evt_decode_events $hevts -message -ignorestring None.] -format dict] { |
||||
if {[info exists opts(count)] && |
||||
[incr opts(count) -1] < 0} { |
||||
return |
||||
} |
||||
puts $opts(outfd) "[dict get $ev -timecreated] [dict get $ev -eventrecordid] [dict get $ev -providername]: [dict get $ev -eventrecordid] [dict get $ev -message]" |
||||
} |
||||
} finally { |
||||
evt_close {*}$hevts |
||||
} |
||||
} |
||||
} finally { |
||||
evt_close $hq |
||||
} |
||||
} |
@ -0,0 +1,236 @@
|
||||
# |
||||
# Copyright (c) 2010, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi { |
||||
# Array maps handles we are waiting on to the ids of the registered waits |
||||
variable _wait_handle_ids |
||||
# Array maps id of registered wait to the corresponding callback scripts |
||||
variable _wait_handle_scripts |
||||
|
||||
} |
||||
|
||||
proc twapi::cast_handle {h type} { |
||||
# TBD - should this use pointer_from_address: |
||||
# return [pointer_from_address [address_from_pointer $h] $type] |
||||
return [list [lindex $h 0] $type] |
||||
} |
||||
|
||||
proc twapi::close_handle {h} { |
||||
|
||||
# Cancel waits on the handle, if any |
||||
cancel_wait_on_handle $h |
||||
|
||||
# Then close it |
||||
CloseHandle $h |
||||
} |
||||
|
||||
# Close multiple handles. In case of errors, collects them but keeps |
||||
# closing remaining handles and only raises the error at the end. |
||||
proc twapi::close_handles {args} { |
||||
# The original definition for this was broken in that it would |
||||
# gracefully accept non list parameters as a list of one. In 3.0 |
||||
# the handle format has changed so this does not happen |
||||
# naturally. We have to try and decipher whether it is a list |
||||
# of handles or a single handle. |
||||
|
||||
foreach arg $args { |
||||
if {[pointer? $arg]} { |
||||
# Looks like a single handle |
||||
if {[catch {close_handle $arg} msg]} { |
||||
set erinfo $::errorInfo |
||||
set ercode $::errorCode |
||||
set ermsg $msg |
||||
} |
||||
} else { |
||||
# Assume a list of handles |
||||
foreach h $arg { |
||||
if {[catch {close_handle $h} msg]} { |
||||
set erinfo $::errorInfo |
||||
set ercode $::errorCode |
||||
set ermsg $msg |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if {[info exists erinfo]} { |
||||
error $msg $erinfo $ercode |
||||
} |
||||
} |
||||
|
||||
# |
||||
# Wait on a handle |
||||
proc twapi::wait_on_handle {hwait args} { |
||||
variable _wait_handle_ids |
||||
variable _wait_handle_scripts |
||||
|
||||
# When we are invoked from callback, handle is always typed as HANDLE |
||||
# so convert it so lookups succeed |
||||
set h [cast_handle $hwait HANDLE] |
||||
|
||||
# 0x00000008 -> # WT_EXECUTEONCEONLY |
||||
array set opts [parseargs args { |
||||
{wait.int -1} |
||||
async.arg |
||||
{executeonce.bool false 0x00000008} |
||||
}] |
||||
|
||||
if {![info exists opts(async)]} { |
||||
if {[info exists _wait_handle_ids($h)]} { |
||||
error "Attempt to synchronously wait on handle that is registered for an asynchronous wait." |
||||
} |
||||
|
||||
set ret [WaitForSingleObject $h $opts(wait)] |
||||
if {$ret == 0x80} { |
||||
return abandoned |
||||
} elseif {$ret == 0} { |
||||
return signalled |
||||
} elseif {$ret == 0x102} { |
||||
return timeout |
||||
} else { |
||||
error "Unexpected value $ret returned from WaitForSingleObject" |
||||
} |
||||
} |
||||
|
||||
# async option specified |
||||
|
||||
# Do not wait on manual reset events as cpu will spin continuously |
||||
# queueing events |
||||
if {[pointer? $hwait HANDLE_MANUALRESETEVENT] && |
||||
! $opts(executeonce) |
||||
} { |
||||
error "A handle to a manual reset event cannot be waited on asynchronously unless -executeonce is specified." |
||||
} |
||||
|
||||
# If handle already registered, cancel previous registration. |
||||
if {[info exists _wait_handle_ids($h)]} { |
||||
cancel_wait_on_handle $h |
||||
} |
||||
|
||||
|
||||
set id [Twapi_RegisterWaitOnHandle $h $opts(wait) $opts(executeonce)] |
||||
|
||||
# Set now that successfully registered |
||||
set _wait_handle_scripts($id) $opts(async) |
||||
set _wait_handle_ids($h) $id |
||||
|
||||
return |
||||
} |
||||
|
||||
# |
||||
# Cancel an async wait on a handle |
||||
proc twapi::cancel_wait_on_handle {h} { |
||||
variable _wait_handle_ids |
||||
variable _wait_handle_scripts |
||||
|
||||
if {[info exists _wait_handle_ids($h)]} { |
||||
Twapi_UnregisterWaitOnHandle $_wait_handle_ids($h) |
||||
unset _wait_handle_scripts($_wait_handle_ids($h)) |
||||
unset _wait_handle_ids($h) |
||||
} |
||||
} |
||||
|
||||
# |
||||
# Called from C when a handle is signalled or times out |
||||
proc twapi::_wait_handler {id h event} { |
||||
variable _wait_handle_ids |
||||
variable _wait_handle_scripts |
||||
|
||||
# We ignore the following stale event cases - |
||||
# - _wait_handle_ids($h) does not exist : the wait was canceled while |
||||
# and event was queued |
||||
# - _wait_handle_ids($h) exists but is different from $id - same |
||||
# as prior case, except that a new wait has since been initiated |
||||
# on the same handle value (which might have be for a different |
||||
# resource |
||||
|
||||
if {[info exists _wait_handle_ids($h)] && |
||||
$_wait_handle_ids($h) == $id} { |
||||
uplevel #0 [linsert $_wait_handle_scripts($id) end $h $event] |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
# Get the handle for a Tcl channel |
||||
proc twapi::get_tcl_channel_handle {chan direction} { |
||||
set direction [expr {[string equal $direction "write"] ? 1 : 0}] |
||||
return [Tcl_GetChannelHandle $chan $direction] |
||||
} |
||||
|
||||
# Duplicate a OS handle |
||||
proc twapi::duplicate_handle {h args} { |
||||
variable my_process_handle |
||||
|
||||
array set opts [parseargs args { |
||||
sourcepid.int |
||||
targetpid.int |
||||
access.arg |
||||
inherit |
||||
closesource |
||||
} -maxleftover 0] |
||||
|
||||
# Assume source and target processes are us |
||||
set source_ph $my_process_handle |
||||
set target_ph $my_process_handle |
||||
|
||||
if {[string is wideinteger $h]} { |
||||
set h [pointer_from_address $h HANDLE] |
||||
} |
||||
|
||||
trap { |
||||
set me [pid] |
||||
# If source pid specified and is not us, get a handle to the process |
||||
if {[info exists opts(sourcepid)] && $opts(sourcepid) != $me} { |
||||
set source_ph [get_process_handle $opts(sourcepid) -access process_dup_handle] |
||||
} |
||||
|
||||
# Ditto for target process... |
||||
if {[info exists opts(targetpid)] && $opts(targetpid) != $me} { |
||||
set target_ph [get_process_handle $opts(targetpid) -access process_dup_handle] |
||||
} |
||||
|
||||
# Do we want to close the original handle (DUPLICATE_CLOSE_SOURCE) |
||||
set flags [expr {$opts(closesource) ? 0x1: 0}] |
||||
|
||||
if {[info exists opts(access)]} { |
||||
set access [_access_rights_to_mask $opts(access)] |
||||
} else { |
||||
# If no desired access is indicated, we want the same access as |
||||
# the original handle |
||||
set access 0 |
||||
set flags [expr {$flags | 0x2}]; # DUPLICATE_SAME_ACCESS |
||||
} |
||||
|
||||
|
||||
set dup [DuplicateHandle $source_ph $h $target_ph $access $opts(inherit) $flags] |
||||
|
||||
# IF targetpid specified, return handle else literal |
||||
# (even if targetpid is us) |
||||
if {[info exists opts(targetpid)]} { |
||||
set dup [pointer_to_address $dup] |
||||
} |
||||
} finally { |
||||
if {$source_ph != $my_process_handle} { |
||||
CloseHandle $source_ph |
||||
} |
||||
if {$target_ph != $my_process_handle} { |
||||
CloseHandle $source_ph |
||||
} |
||||
} |
||||
|
||||
return $dup |
||||
} |
||||
|
||||
proc twapi::set_handle_inheritance {h inherit} { |
||||
# 1 -> HANDLE_FLAG_INHERIT |
||||
SetHandleInformation $h 0x1 [expr {$inherit ? 1 : 0}] |
||||
} |
||||
|
||||
proc twapi::get_handle_inheritance {h} { |
||||
# 1 -> HANDLE_FLAG_INHERIT |
||||
return [expr {[GetHandleInformation $h] & 1}] |
||||
} |
@ -0,0 +1,623 @@
|
||||
# |
||||
# Copyright (c) 2012 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
package require twapi_ui; # SetCursorPos etc. |
||||
|
||||
# Enable window input |
||||
proc twapi::enable_window_input {hwin} { |
||||
return [expr {[EnableWindow $hwin 1] != 0}] |
||||
} |
||||
|
||||
# Disable window input |
||||
proc twapi::disable_window_input {hwin} { |
||||
return [expr {[EnableWindow $hwin 0] != 0}] |
||||
} |
||||
|
||||
# CHeck if window input is enabled |
||||
proc twapi::window_input_enabled {hwin} { |
||||
return [IsWindowEnabled $hwin] |
||||
} |
||||
|
||||
# Simulate user input |
||||
proc twapi::send_input {inputlist} { |
||||
array set input_defs { |
||||
MOUSEEVENTF_MOVE 0x0001 |
||||
MOUSEEVENTF_LEFTDOWN 0x0002 |
||||
MOUSEEVENTF_LEFTUP 0x0004 |
||||
MOUSEEVENTF_RIGHTDOWN 0x0008 |
||||
MOUSEEVENTF_RIGHTUP 0x0010 |
||||
MOUSEEVENTF_MIDDLEDOWN 0x0020 |
||||
MOUSEEVENTF_MIDDLEUP 0x0040 |
||||
MOUSEEVENTF_XDOWN 0x0080 |
||||
MOUSEEVENTF_XUP 0x0100 |
||||
MOUSEEVENTF_WHEEL 0x0800 |
||||
MOUSEEVENTF_VIRTUALDESK 0x4000 |
||||
MOUSEEVENTF_ABSOLUTE 0x8000 |
||||
|
||||
KEYEVENTF_EXTENDEDKEY 0x0001 |
||||
KEYEVENTF_KEYUP 0x0002 |
||||
KEYEVENTF_UNICODE 0x0004 |
||||
KEYEVENTF_SCANCODE 0x0008 |
||||
|
||||
XBUTTON1 0x0001 |
||||
XBUTTON2 0x0002 |
||||
} |
||||
|
||||
set inputs [list ] |
||||
foreach input $inputlist { |
||||
if {[string equal [lindex $input 0] "mouse"]} { |
||||
lassign $input mouse xpos ypos |
||||
set mouseopts [lrange $input 3 end] |
||||
array unset opts |
||||
array set opts [parseargs mouseopts { |
||||
relative moved |
||||
ldown lup rdown rup mdown mup x1down x1up x2down x2up |
||||
wheel.int |
||||
}] |
||||
set flags 0 |
||||
if {! $opts(relative)} { |
||||
set flags $input_defs(MOUSEEVENTF_ABSOLUTE) |
||||
} |
||||
|
||||
if {[info exists opts(wheel)]} { |
||||
if {($opts(x1down) || $opts(x1up) || $opts(x2down) || $opts(x2up))} { |
||||
error "The -wheel input event attribute may not be specified with -x1up, -x1down, -x2up or -x2down events" |
||||
} |
||||
set mousedata $opts(wheel) |
||||
set flags $input_defs(MOUSEEVENTF_WHEEL) |
||||
} else { |
||||
if {$opts(x1down) || $opts(x1up)} { |
||||
if {$opts(x2down) || $opts(x2up)} { |
||||
error "The -x1down, -x1up mouse input attributes are mutually exclusive with -x2down, -x2up attributes" |
||||
} |
||||
set mousedata $input_defs(XBUTTON1) |
||||
} else { |
||||
if {$opts(x2down) || $opts(x2up)} { |
||||
set mousedata $input_defs(XBUTTON2) |
||||
} else { |
||||
set mousedata 0 |
||||
} |
||||
} |
||||
} |
||||
foreach {opt flag} { |
||||
moved MOVE |
||||
ldown LEFTDOWN |
||||
lup LEFTUP |
||||
rdown RIGHTDOWN |
||||
rup RIGHTUP |
||||
mdown MIDDLEDOWN |
||||
mup MIDDLEUP |
||||
x1down XDOWN |
||||
x1up XUP |
||||
x2down XDOWN |
||||
x2up XUP |
||||
} { |
||||
if {$opts($opt)} { |
||||
set flags [expr {$flags | $input_defs(MOUSEEVENTF_$flag)}] |
||||
} |
||||
} |
||||
|
||||
lappend inputs [list mouse $xpos $ypos $mousedata $flags] |
||||
|
||||
} else { |
||||
lassign $input inputtype vk scan keyopts |
||||
if {"-extended" ni $keyopts} { |
||||
set extended 0 |
||||
} else { |
||||
set extended $input_defs(KEYEVENTF_EXTENDEDKEY) |
||||
} |
||||
if {"-usescan" ni $keyopts} { |
||||
set usescan 0 |
||||
} else { |
||||
set usescan $input_defs(KEYEVENTF_SCANCODE) |
||||
} |
||||
switch -exact -- $inputtype { |
||||
keydown { |
||||
lappend inputs [list key $vk $scan [expr {$extended|$usescan}]] |
||||
} |
||||
keyup { |
||||
lappend inputs [list key $vk $scan \ |
||||
[expr {$extended |
||||
| $usescan |
||||
| $input_defs(KEYEVENTF_KEYUP) |
||||
}]] |
||||
} |
||||
key { |
||||
lappend inputs [list key $vk $scan [expr {$extended|$usescan}]] |
||||
lappend inputs [list key $vk $scan \ |
||||
[expr {$extended |
||||
| $usescan |
||||
| $input_defs(KEYEVENTF_KEYUP) |
||||
}]] |
||||
} |
||||
unicode { |
||||
lappend inputs [list key 0 $scan $input_defs(KEYEVENTF_UNICODE)] |
||||
lappend inputs [list key 0 $scan \ |
||||
[expr {$input_defs(KEYEVENTF_UNICODE) |
||||
| $input_defs(KEYEVENTF_KEYUP) |
||||
}]] |
||||
} |
||||
default { |
||||
error "Unknown input type '$inputtype'" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
SendInput $inputs |
||||
} |
||||
|
||||
# Block the input |
||||
proc twapi::block_input {} { |
||||
return [BlockInput 1] |
||||
} |
||||
|
||||
# Unblock the input |
||||
proc twapi::unblock_input {} { |
||||
return [BlockInput 0] |
||||
} |
||||
|
||||
# Send the given set of characters to the input queue |
||||
proc twapi::send_input_text {s} { |
||||
return [Twapi_SendUnicode $s] |
||||
} |
||||
|
||||
# send_keys - uses same syntax as VB SendKeys function |
||||
proc twapi::send_keys {keys} { |
||||
set inputs [_parse_send_keys $keys] |
||||
send_input $inputs |
||||
} |
||||
|
||||
|
||||
# Handles a hotkey notification |
||||
proc twapi::_hotkey_handler {msg atom key msgpos ticks} { |
||||
variable _hotkeys |
||||
|
||||
# Note it is not an error if a hotkey does not exist since it could |
||||
# have been deregistered in the time between hotkey input and receiving it. |
||||
set code 0 |
||||
if {[info exists _hotkeys($atom)]} { |
||||
foreach handler $_hotkeys($atom) { |
||||
set code [catch {uplevel #0 $handler} msg] |
||||
switch -exact -- $code { |
||||
0 { |
||||
# Normal, keep going |
||||
} |
||||
1 { |
||||
# Error - put in background and abort |
||||
after 0 [list error $msg $::errorInfo $::errorCode] |
||||
break |
||||
} |
||||
3 { |
||||
break; # Ignore remaining handlers |
||||
} |
||||
default { |
||||
# Keep going |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return -code $code "" |
||||
} |
||||
|
||||
proc twapi::register_hotkey {hotkey script args} { |
||||
variable _hotkeys |
||||
|
||||
# 0x312 -> WM_HOTKEY |
||||
_register_script_wm_handler 0x312 [list [namespace current]::_hotkey_handler] 1 |
||||
|
||||
array set opts [parseargs args { |
||||
append |
||||
} -maxleftover 0] |
||||
|
||||
# set script [lrange $script 0 end]; # Ensure a valid list |
||||
|
||||
lassign [_hotkeysyms_to_vk $hotkey] modifiers vk |
||||
set hkid "twapi_hk_${vk}_$modifiers" |
||||
set atom [GlobalAddAtom $hkid] |
||||
if {[info exists _hotkeys($atom)]} { |
||||
GlobalDeleteAtom $atom; # Undo above AddAtom since already there |
||||
if {$opts(append)} { |
||||
lappend _hotkeys($atom) $script |
||||
} else { |
||||
set _hotkeys($atom) [list $script]; # Replace previous script |
||||
} |
||||
return $atom |
||||
} |
||||
trap { |
||||
RegisterHotKey $atom $modifiers $vk |
||||
} onerror {} { |
||||
GlobalDeleteAtom $atom; # Undo above AddAtom |
||||
rethrow |
||||
} |
||||
set _hotkeys($atom) [list $script]; # Replace previous script |
||||
return $atom |
||||
} |
||||
|
||||
proc twapi::unregister_hotkey {atom} { |
||||
variable _hotkeys |
||||
if {[info exists _hotkeys($atom)]} { |
||||
UnregisterHotKey $atom |
||||
GlobalDeleteAtom $atom |
||||
unset _hotkeys($atom) |
||||
} |
||||
} |
||||
|
||||
|
||||
# Simulate clicking a mouse button |
||||
proc twapi::click_mouse_button {button} { |
||||
switch -exact -- $button { |
||||
1 - |
||||
left { set down -ldown ; set up -lup} |
||||
2 - |
||||
right { set down -rdown ; set up -rup} |
||||
3 - |
||||
middle { set down -mdown ; set up -mup} |
||||
x1 { set down -x1down ; set up -x1up} |
||||
x2 { set down -x2down ; set up -x2up} |
||||
default {error "Invalid mouse button '$button' specified"} |
||||
} |
||||
|
||||
send_input [list \ |
||||
[list mouse 0 0 $down] \ |
||||
[list mouse 0 0 $up]] |
||||
return |
||||
} |
||||
|
||||
# Simulate mouse movement |
||||
proc twapi::move_mouse {xpos ypos {mode ""}} { |
||||
# If mouse trails are enabled, it leaves traces when the mouse is |
||||
# moved and does not clear them until mouse is moved again. So |
||||
# we temporarily disable mouse trails if we can |
||||
|
||||
if {[llength [info commands ::twapi::get_system_parameters_info]] != 0} { |
||||
set trail [get_system_parameters_info SPI_GETMOUSETRAILS] |
||||
set_system_parameters_info SPI_SETMOUSETRAILS 0 |
||||
} |
||||
switch -exact -- $mode { |
||||
-relative { |
||||
lappend cmd -relative |
||||
lassign [GetCursorPos] curx cury |
||||
incr xpos $curx |
||||
incr ypos $cury |
||||
} |
||||
-absolute - |
||||
"" { } |
||||
default { error "Invalid mouse movement mode '$mode'" } |
||||
} |
||||
|
||||
SetCursorPos $xpos $ypos |
||||
|
||||
# Restore trail setting if we had disabled it and it was originally enabled |
||||
if {[info exists trail] && $trail} { |
||||
set_system_parameters_info SPI_SETMOUSETRAILS $trail |
||||
} |
||||
} |
||||
|
||||
# Simulate turning of the mouse wheel |
||||
proc twapi::turn_mouse_wheel {wheelunits} { |
||||
send_input [list [list mouse 0 0 -relative -wheel $wheelunits]] |
||||
return |
||||
} |
||||
|
||||
# Get the mouse/cursor position |
||||
proc twapi::get_mouse_location {} { |
||||
return [GetCursorPos] |
||||
} |
||||
|
||||
proc twapi::get_input_idle_time {} { |
||||
# The formats are to convert wrapped 32bit signed to unsigned |
||||
set last_event [format 0x%x [GetLastInputInfo]] |
||||
set now [format 0x%x [GetTickCount]] |
||||
|
||||
# Deal with wrap around |
||||
if {$now >= $last_event} { |
||||
return [expr {$now - $last_event}] |
||||
} else { |
||||
return [expr {$now + (0xffffffff - $last_event) + 1}] |
||||
} |
||||
} |
||||
|
||||
# Initialize the virtual key table |
||||
proc twapi::_init_vk_map {} { |
||||
variable vk_map |
||||
|
||||
if {![info exists vk_map]} { |
||||
# Map tokens to VK_* key codes |
||||
array set vk_map { |
||||
BACK {0x08 0} |
||||
BACKSPACE {0x08 0} BS {0x08 0} BKSP {0x08 0} TAB {0x09 0} |
||||
CLEAR {0x0C 0} RETURN {0x0D 0} ENTER {0x0D 0} SHIFT {0x10 0} |
||||
CONTROL {0x11 0} MENU {0x12 0} ALT {0x12 0} PAUSE {0x13 0} |
||||
BREAK {0x13 0} CAPITAL {0x14 0} CAPSLOCK {0x14 0} |
||||
KANA {0x15 0} HANGEUL {0x15 0} HANGUL {0x15 0} JUNJA {0x17 0} |
||||
FINAL {0x18 0} HANJA {0x19 0} KANJI {0x19 0} ESCAPE {0x1B 0} |
||||
ESC {0x1B 0} CONVERT {0x1C 0} NONCONVERT {0x1D 0} |
||||
ACCEPT {0x1E 0} MODECHANGE {0x1F 0} SPACE {0x20 0} |
||||
PRIOR {0x21 0} PGUP {0x21 0} NEXT {0x22 0} PGDN {0x22 0} |
||||
END {0x23 0} HOME {0x24 0} LEFT {0x25 0} UP {0x26 0} |
||||
RIGHT {0x27 0} DOWN {0x28 0} SELECT {0x29 0} |
||||
PRINT {0x2A 0} PRTSC {0x2C 0} EXECUTE {0x2B 0} |
||||
SNAPSHOT {0x2C 0} INSERT {0x2D 0} INS {0x2D 0} |
||||
DELETE {0x2E 0} DEL {0x2E 0} HELP {0x2F 0} LWIN {0x5B 0} |
||||
RWIN {0x5C 0} APPS {0x5D 0} SLEEP {0x5F 0} NUMPAD0 {0x60 0} |
||||
NUMPAD1 {0x61 0} NUMPAD2 {0x62 0} NUMPAD3 {0x63 0} |
||||
NUMPAD4 {0x64 0} NUMPAD5 {0x65 0} NUMPAD6 {0x66 0} |
||||
NUMPAD7 {0x67 0} NUMPAD8 {0x68 0} NUMPAD9 {0x69 0} |
||||
MULTIPLY {0x6A 0} ADD {0x6B 0} SEPARATOR {0x6C 0} |
||||
SUBTRACT {0x6D 0} DECIMAL {0x6E 0} DIVIDE {0x6F 0} |
||||
F1 {0x70 0} F2 {0x71 0} F3 {0x72 0} F4 {0x73 0} |
||||
F5 {0x74 0} F6 {0x75 0} F7 {0x76 0} F8 {0x77 0} |
||||
F9 {0x78 0} F10 {0x79 0} F11 {0x7A 0} F12 {0x7B 0} |
||||
F13 {0x7C 0} F14 {0x7D 0} F15 {0x7E 0} F16 {0x7F 0} |
||||
F17 {0x80 0} F18 {0x81 0} F19 {0x82 0} F20 {0x83 0} |
||||
F21 {0x84 0} F22 {0x85 0} F23 {0x86 0} F24 {0x87 0} |
||||
NUMLOCK {0x90 0} SCROLL {0x91 0} SCROLLLOCK {0x91 0} |
||||
LSHIFT {0xA0 0} RSHIFT {0xA1 0 -extended} LCONTROL {0xA2 0} |
||||
RCONTROL {0xA3 0 -extended} LMENU {0xA4 0} LALT {0xA4 0} |
||||
RMENU {0xA5 0 -extended} RALT {0xA5 0 -extended} |
||||
BROWSER_BACK {0xA6 0} BROWSER_FORWARD {0xA7 0} |
||||
BROWSER_REFRESH {0xA8 0} BROWSER_STOP {0xA9 0} |
||||
BROWSER_SEARCH {0xAA 0} BROWSER_FAVORITES {0xAB 0} |
||||
BROWSER_HOME {0xAC 0} VOLUME_MUTE {0xAD 0} |
||||
VOLUME_DOWN {0xAE 0} VOLUME_UP {0xAF 0} |
||||
MEDIA_NEXT_TRACK {0xB0 0} MEDIA_PREV_TRACK {0xB1 0} |
||||
MEDIA_STOP {0xB2 0} MEDIA_PLAY_PAUSE {0xB3 0} |
||||
LAUNCH_MAIL {0xB4 0} LAUNCH_MEDIA_SELECT {0xB5 0} |
||||
LAUNCH_APP1 {0xB6 0} LAUNCH_APP2 {0xB7 0} |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Find the next token from a send_keys argument |
||||
# Returns pair token,position after token |
||||
proc twapi::_parse_send_key_token {keys start} { |
||||
set char [string index $keys $start] |
||||
if {$char ne "\{"} { |
||||
return [list $char [incr start]] |
||||
} |
||||
# Need to find the matching end brace. Note special case of |
||||
# start/end brace enclosed within braces |
||||
set n [string length $keys] |
||||
# Jump past brace and succeeding character (which may be end brace) |
||||
set terminator [string first "\}" $keys $start+2] |
||||
if {$terminator < 0} { |
||||
error "Unterminated or empty braced key token." |
||||
} |
||||
return [list [string range $keys $start $terminator] [incr terminator]] |
||||
} |
||||
|
||||
# Appends to inputs the trailer in reverse order. trailer is reset |
||||
proc twapi::_flush_send_keys_trailer {vinputs vtrailer} { |
||||
upvar 1 $vinputs inputs |
||||
upvar 1 $vtrailer trailer |
||||
|
||||
lappend inputs {*}[lreverse $trailer] |
||||
set trailer {} |
||||
} |
||||
|
||||
# Constructs a list of input events by parsing a string in the format |
||||
# used by Visual Basic's SendKeys function. See that documentation |
||||
# for syntax. |
||||
proc twapi::_parse_send_keys {keys} { |
||||
variable vk_map |
||||
|
||||
_init_vk_map |
||||
array set modifier_vk {+ 0x10 ^ 0x11 % 0x12} |
||||
|
||||
# Array state holds state of the parse. An atom refers to a single |
||||
# character or a () group. |
||||
# modifiers - list of current modifiers in order they were added including |
||||
# those coming from containing groups. |
||||
# group_modifiers - stack of modifiers state when parsing groups. |
||||
# When a group begins, state(modifiers) is pushed on this stack. |
||||
# The top of the stack is used to initialize state(modifiers) |
||||
# for every atom within the group. When the group ends, |
||||
# the top of the stack is popped and discarded and state(modifiers) |
||||
# is reinitialized to new top of stack. |
||||
# trailer - list of trailing input records to add after next atom. Note |
||||
# these are stored in order of occurence but need to be reversed |
||||
# when emitted |
||||
# group_trailers - stack of trailers to add after group ends. Each |
||||
# element is a trailer which is a list of input records. |
||||
# cleanup_trailer - to be emitted right at the end if we have to |
||||
# reset CAPSLOCK/NUMLOCK/SCROLL |
||||
set state(modifiers) {} |
||||
set state(group_modifiers) [list $state(modifiers)]; # "Global" group |
||||
set state(trailer) {} |
||||
set state(group_trailers) {} |
||||
set state(cleanup_trailer) {} |
||||
|
||||
set inputs {} |
||||
|
||||
# If {CAPS,NUM,SCROLL}LOCK are set, need to reset them and then |
||||
# set them back |
||||
foreach vk {20 144 145} { |
||||
if {[GetKeyState $vk]} { |
||||
lappend inputs [list key $vk 0] |
||||
lappend state(cleanup_trailer) [list key $vk 0] |
||||
} |
||||
} |
||||
|
||||
set keyslen [string length $keys] |
||||
set pos 0; # Current parse position |
||||
while {$pos < $keyslen} { |
||||
lassign [_parse_send_key_token $keys $pos] token pos |
||||
switch -exact -- $token { |
||||
+ - |
||||
^ - |
||||
% { |
||||
if {$token in $state(modifiers)} { |
||||
# Following VB SendKeys |
||||
error "Modifier state for $token already set." |
||||
} |
||||
lappend state(modifiers) $token |
||||
lappend inputs [list keydown $modifier_vk($token) 0] |
||||
lappend state(trailer) [list keyup $modifier_vk($token) 0] |
||||
} |
||||
"(" { |
||||
# Start a group |
||||
lappend state(group_modifiers) $state(modifiers) |
||||
lappend state(group_trailers) $state(trailer) |
||||
set state(trailer) {} |
||||
} |
||||
")" { |
||||
# Terminates group. Illegal if no group collection in progress |
||||
if {[llength $state(group_trailers)] == 0} { |
||||
error "Unmatched \")\" in send_keys string." |
||||
} |
||||
# If there is a live trailer inside group, emit it e.g. +(ab^) |
||||
_flush_send_keys_trailer inputs state(trailer) |
||||
# Now emit the group trailer |
||||
set trailer [lpop state(group_trailers)] |
||||
_flush_send_keys_trailer inputs trailer |
||||
# Discard the initial modifier state for this group |
||||
lpop state(group_modifiers) |
||||
# Set the current modifiers to outer group state |
||||
set state(modifiers) [lindex $state(group_modifiers) end] |
||||
} |
||||
default { |
||||
if {$token eq "~"} { |
||||
set token "{ENTER}" |
||||
} |
||||
# May be a single character to send, a braced virtual key |
||||
# or a braced single char with count |
||||
if {[string length $token] == 1} { |
||||
# Single character. |
||||
set key $token |
||||
set nch 1 |
||||
} elseif {[string index $token 0] eq "\{"} { |
||||
# NOTE: a ~ inside a brace is treated as a literal ~ |
||||
# and not the ENTER key |
||||
# Look for space skipping the starting brace and following |
||||
# character which may be itself a space (to be repeated) |
||||
set space_pos [string first " " $token 2] |
||||
if {$space_pos < 0} { |
||||
# No space found |
||||
set nch 1 |
||||
set key [string range $token 1 end-1] |
||||
} else { |
||||
# A key followed by a count |
||||
# Note space_pos >= 2 |
||||
set key [string range $token 1 $space_pos-1] |
||||
set nch [string trim [string range $token $space_pos+1 end-1]] |
||||
if {![string is integer -strict $nch] || $nch < 0} { |
||||
error "Invalid count \"$nch\" in send_keys." |
||||
} |
||||
} |
||||
} else { |
||||
# Problem in token parsing. Would be a bug. |
||||
error "Internal error: invalid token \"$token\" parsing send_keys string." |
||||
} |
||||
|
||||
set vk_leader {} |
||||
set vk_trailer {} |
||||
if {[string length $key] == 1} { |
||||
# Single character |
||||
lassign [VkKeyScan $key] modifiers vk |
||||
if {$modifiers == -1 || $vk == -1} { |
||||
scan $key %c code_point |
||||
set vk_rec [list unicode 0 $code_point] |
||||
} else { |
||||
# Generates input records for modifiers that are set |
||||
# unless they are already set. NOTE: Do NOT set the |
||||
# state(modifier) state since they will be in effect |
||||
# only for the current character. This is for correctly |
||||
# showing A-Z with shift and Ctrl-A etc. with control. |
||||
if {($modifiers & 0x1) && ("+" ni $state(modifiers))} { |
||||
lappend vk_leader [list keydown 0x10 0] |
||||
lappend vk_trailer [list keyup 0x10 0] |
||||
} |
||||
if {($modifiers & 0x2) && ("^" ni $state(modifiers))} { |
||||
lappend vk_leader [list keydown 0x11 0] |
||||
lappend vk_trailer [list keyup 0x11 0] |
||||
} |
||||
|
||||
if {($modifiers & 0x4) && ("%" ni $state(modifiers))} { |
||||
lappend vk_leader [list keydown 0x12 0] |
||||
lappend vk_trailer [list keyup 0x12 0] |
||||
} |
||||
set vk_rec [list key $vk 0] |
||||
} |
||||
} else { |
||||
# Virtual key string. Note modifiers ignored here |
||||
# as for VB SendKeys |
||||
if {[info exists vk_map($key)]} { |
||||
# Virtual key |
||||
set vk_rec [list key {*}$vk_map($key)] |
||||
} else { |
||||
error "Unknown braced virtual key \"$token\"." |
||||
} |
||||
} |
||||
lappend inputs {*}$vk_leader |
||||
lappend inputs {*}[lrepeat $nch $vk_rec] |
||||
# vk_trailer arises from the character itself, e.g. A |
||||
# has shift set, Ctrl-A has control set. |
||||
_flush_send_keys_trailer inputs vk_trailer |
||||
# state(trailer) arises from preceding +,^,% This is also |
||||
# emitted and reset as it applied only to this character |
||||
_flush_send_keys_trailer inputs state(trailer) |
||||
set state(modifiers) [lindex $state(group_modifiers) end] |
||||
} |
||||
} |
||||
} |
||||
# Emit left over trailer |
||||
_flush_send_keys_trailer inputs state(trailer) |
||||
|
||||
# Restore capslock/numlock |
||||
_flush_send_keys_trailer inputs state(cleanup_trailer) |
||||
|
||||
return $inputs |
||||
} |
||||
|
||||
# utility procedure to map symbolic hotkey to {modifiers virtualkey} |
||||
# We allow modifier map to be passed in because different api's use |
||||
# different bits for key modifiers |
||||
proc twapi::_hotkeysyms_to_vk {hotkey {modifier_map {ctrl 2 control 2 alt 1 menu 1 shift 4 win 8}}} { |
||||
variable vk_map |
||||
|
||||
_init_vk_map |
||||
|
||||
set keyseq [split [string tolower $hotkey] -] |
||||
set key [lindex $keyseq end] |
||||
|
||||
# Convert modifiers to bitmask |
||||
set modifiers 0 |
||||
foreach modifier [lrange $keyseq 0 end-1] { |
||||
setbits modifiers [dict! $modifier_map [string tolower $modifier]] |
||||
} |
||||
# Map the key to a virtual key code |
||||
if {[string length $key] == 1} { |
||||
# Single character |
||||
scan $key %c unicode |
||||
|
||||
# Only allow alphanumeric keys and a few punctuation symbols |
||||
# since keyboard layouts are not standard |
||||
if {$unicode >= 0x61 && $unicode <= 0x7A} { |
||||
# Lowercase letters - change to upper case virtual keys |
||||
set vk [expr {$unicode-32}] |
||||
} elseif {($unicode >= 0x30 && $unicode <= 0x39) |
||||
|| ($unicode >= 0x41 && $unicode <= 0x5A)} { |
||||
# Digits or upper case |
||||
set vk $unicode |
||||
} else { |
||||
error "Only alphanumeric characters may be specified for the key. For non-alphanumeric characters, specify the virtual key code" |
||||
} |
||||
} elseif {[info exists vk_map($key)]} { |
||||
# It is a virtual key name |
||||
set vk [lindex $vk_map($key) 0] |
||||
} elseif {[info exists vk_map([string toupper $key])]} { |
||||
# It is a virtual key name |
||||
set vk [lindex $vk_map([string toupper $key]) 0] |
||||
} elseif {[string is integer -strict $key]} { |
||||
# Actual virtual key specification |
||||
set vk $key |
||||
} else { |
||||
error "Unknown or invalid key specifier '$key'" |
||||
} |
||||
|
||||
return [list $modifiers $vk] |
||||
} |
@ -0,0 +1,605 @@
|
||||
# MeTOO stands for "MeTOO Emulates TclOO" (at a superficial syntactic level) |
||||
# |
||||
# Implements a *tiny*, but useful, subset of TclOO, primarily for use |
||||
# with Tcl 8.4. Intent is that if you write code using MeToo, it should work |
||||
# unmodified with TclOO in 8.5/8.6. Obviously, don't try going the other way! |
||||
# |
||||
# Emulation is superficial, don't try to be too clever in usage. |
||||
# Doing funky, or even non-funky, things with object namespaces will |
||||
# not work as you would expect. |
||||
# |
||||
# See the metoo::demo proc for sample usage. Calling this proc |
||||
# with parameter "oo" will use the TclOO commands. Else the metoo:: |
||||
# commands. Note the demo code remains the same for both. |
||||
# |
||||
# The following fragment uses MeToo only if TclOO is not available: |
||||
# if {[llength [info commands oo::*]]} { |
||||
# namespace import oo::* |
||||
# } else { |
||||
# source metoo.tcl |
||||
# namespace import metoo::class |
||||
# } |
||||
# class create C {...} |
||||
# |
||||
# Summary of the TclOO subset implemented - see TclOO docs for detail : |
||||
# |
||||
# Creating a new class: |
||||
# metoo::class create CLASSNAME CLASSDEFINITION |
||||
# |
||||
# Destroying a class: |
||||
# CLASSNAME destroy |
||||
# - this also destroys objects of that class and recursively destroys |
||||
# child classes. NOTE: deleting the class namespace or renaming |
||||
# the CLASSNAME command to "" will NOT call object destructors. |
||||
# |
||||
# CLASSDEFINITION: Following may appear in CLASSDEFINTION |
||||
# method METHODNAME params METHODBODY |
||||
# - same as TclOO |
||||
# constructor params METHODBODY |
||||
# - same syntax as TclOO |
||||
# destructor METHODBODY |
||||
# - same syntax as TclOO |
||||
# unknown METHODNAME ARGS |
||||
# - if defined, called when an undefined method is invoked |
||||
# superclass SUPER |
||||
# - inherits from SUPER. Unlike TclOO, only single inheritance. Also |
||||
# no checks for inheritance loops. You'll find out quickly enough! |
||||
# All other commands within a CLASSDEFINITION will either raise error or |
||||
# work differently from TclOO. Actually you can use pretty much any |
||||
# Tcl command inside CLASSDEFINITION but the results may not be what you |
||||
# expect. Best to avoid this. |
||||
# |
||||
# METHODBODY: The following method-internal TclOO commands are available: |
||||
# my METHODNAME ARGS |
||||
# - to call another method METHODNAME |
||||
# my variable VAR1 ?VAR2...? |
||||
# - brings object-specific variables into scope |
||||
# next ?ARGS? |
||||
# - calls the superclass method of the same name |
||||
# self |
||||
# self object |
||||
# - returns the object name (usable as a command) |
||||
# self class |
||||
# - returns class of this object |
||||
# self namespace |
||||
# - returns namespace of this object |
||||
# |
||||
# Creating objects: |
||||
# CLASSNAME create OBJNAME ?ARGS? |
||||
# - creates object OBJNAME of class CLASSNAME, passing ARGS to constructor |
||||
# Returns the fully qualified object name that can be used as a command. |
||||
# CLASSNAME new ?ARGS? |
||||
# - creates a new object with an auto-generated name |
||||
# |
||||
# Destroying objects |
||||
# OBJNAME destroy |
||||
# - destroys the object calling destructors |
||||
# rename OBJNAME "" |
||||
# - same as above |
||||
# |
||||
# Renaming an object |
||||
# rename OBJNAME NEWNAME |
||||
# - the object can now be invoked using the new name. Note this is unlike |
||||
# classes which should not be renamed. |
||||
# |
||||
# |
||||
# Introspection (though different from TclOO) |
||||
# metoo::introspect object isa OBJECT ?CLASSNAME? |
||||
# - returns 1 if OBJECT is a metoo object and is of the specified class |
||||
# if CLASSNAME is specified. Returns 0 otherwise. |
||||
# metoo::introspect object list |
||||
# - returns list of all objects |
||||
# metoo::introspect class ancestors CLASSNAME |
||||
# - returns list of ancestors for a class |
||||
# |
||||
# Differences and missing features from TclOO: Everything not listed above |
||||
# is missing. Some notable differences: |
||||
# - MeTOO is class-based, not object based like TclOO, thus class instances |
||||
# (objects) cannot be modified by adding instance-specific methods etc.. |
||||
# Also a class is not itself an object. |
||||
# - Renaming classes does not work and will fail in mysterious ways |
||||
# - does not support class refinement/definition |
||||
# - no variable command at class level for automatically bringing variables |
||||
# into scope |
||||
# - no filters, forwarding, multiple-inheritance |
||||
# - no private methods (all methods are exported). |
||||
|
||||
# NOTE: file must be sourced at global level since metoo namespace is expected |
||||
# to be top level namespace |
||||
|
||||
# DO NOT DO THIS. ELSE TESTS FAIL BECAUSE they define tests in the |
||||
# metoo namespace which then get deleted by the line below when |
||||
# the package is lazy auto-loaded |
||||
# catch {namespace delete metoo} |
||||
|
||||
# TBD - variable ("my variable" is done, "variable" in method or |
||||
# class definition is not) |
||||
# TBD - default constructor and destructor to "next" (or maybe that |
||||
# is already taken care of by the inheritance code |
||||
|
||||
namespace eval metoo { |
||||
variable next_id 0 |
||||
|
||||
variable _objects; # Maps objects to its namespace |
||||
array set _objects {} |
||||
|
||||
} |
||||
|
||||
# Namespace in which commands in a class definition block are called |
||||
namespace eval metoo::define { |
||||
proc method {class_ns name params body} { |
||||
# Methods are defined in the methods subspace of the class namespace. |
||||
# We prefix with _m_ to prevent them from being directly called |
||||
# as procs, for example if the method is a Tcl command like "set" |
||||
# The first parameter to a method is always the object namespace |
||||
# denoted as the paramter "_this" |
||||
namespace eval ${class_ns}::methods [list proc _m_$name [concat [list _this] $params] $body] |
||||
|
||||
} |
||||
proc superclass {class_ns superclass} { |
||||
if {[info exists ${class_ns}::super]} { |
||||
error "Only one superclass allowed for a class" |
||||
} |
||||
set sup [uplevel 3 "namespace eval $superclass {namespace current}"] |
||||
set ${class_ns}::super $sup |
||||
# We store the subclass in the super so it can be destroyed |
||||
# if the super is destroyed. |
||||
set ${sup}::subclasses($class_ns) 1 |
||||
} |
||||
proc constructor {class_ns params body} { |
||||
method $class_ns constructor $params $body |
||||
} |
||||
proc destructor {class_ns body} { |
||||
method $class_ns destructor {} $body |
||||
} |
||||
proc export {args} { |
||||
# Nothing to do, all methods are exported anyways |
||||
# Command is here for compatibility only |
||||
} |
||||
} |
||||
|
||||
# Namespace in which commands used in objects methods are defined |
||||
# (self, my etc.) |
||||
namespace eval metoo::object { |
||||
proc next {args} { |
||||
upvar 1 _this this; # object namespace |
||||
|
||||
# Figure out what class context this is executing in. Note |
||||
# we cannot use _this in caller since that is the object namespace |
||||
# which is not necessarily related to the current class namespace. |
||||
set class_ns [namespace parent [uplevel 1 {namespace current}]] |
||||
|
||||
# Figure out the current method being called |
||||
set methodname [namespace tail [lindex [uplevel 1 {info level 0}] 0]] |
||||
|
||||
# Find the next method in the class hierarchy and call it |
||||
while {[info exists ${class_ns}::super]} { |
||||
set class_ns [set ${class_ns}::super] |
||||
if {[llength [info commands ${class_ns}::methods::$methodname]]} { |
||||
return [uplevel 1 [list ${class_ns}::methods::$methodname $this] $args] |
||||
} |
||||
} |
||||
|
||||
error "'next' command has no receiver in the hierarchy for method $methodname" |
||||
} |
||||
|
||||
proc self {{what object}} { |
||||
upvar 1 _this this |
||||
switch -exact -- $what { |
||||
class { return [namespace parent $this] } |
||||
namespace { return $this } |
||||
object { return [set ${this}::_(name)] } |
||||
default { |
||||
error "Argument '$what' not understood by self method" |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc my {methodname args} { |
||||
# We insert the object namespace as the first parameter to the command. |
||||
# This is passed as the first parameter "_this" to methods. Since |
||||
# "my" can be only called from methods, we can retrieve it fro |
||||
# our caller. |
||||
upvar 1 _this this; # object namespace |
||||
|
||||
set class_ns [namespace parent $this] |
||||
|
||||
set meth [::metoo::_locate_method $class_ns $methodname] |
||||
if {$meth ne ""} { |
||||
# We need to invoke in the caller's context so upvar etc. will |
||||
# not be affected by this intermediate method dispatcher |
||||
return [uplevel 1 [list $meth $this] $args] |
||||
} |
||||
|
||||
# It is ok for constructor or destructor to be undefined. For |
||||
# the others, invoke "unknown" if it exists |
||||
if {$methodname eq "constructor" || $methodname eq "destructor"} { |
||||
return |
||||
} |
||||
|
||||
set meth [::metoo::_locate_method $class_ns "unknown"] |
||||
if {$meth ne ""} { |
||||
# We need to invoke in the caller's context so upvar etc. will |
||||
# not be affected by this intermediate method dispatcher |
||||
return [uplevel 1 [list $meth $this $methodname] $args] |
||||
} |
||||
|
||||
error "Unknown method $methodname" |
||||
} |
||||
} |
||||
|
||||
# Given a method name, locate it in the class hierarchy. Returns |
||||
# fully qualified method if found, else an empty string |
||||
proc metoo::_locate_method {class_ns methodname} { |
||||
# See if there is a method defined in this class. |
||||
# Breakage if method names with wildcard chars. Too bad |
||||
if {[llength [info commands ${class_ns}::methods::_m_$methodname]]} { |
||||
# We need to invoke in the caller's context so upvar etc. will |
||||
# not be affected by this intermediate method dispatcher |
||||
return ${class_ns}::methods::_m_$methodname |
||||
} |
||||
|
||||
# No method here, check for super class. |
||||
while {[info exists ${class_ns}::super]} { |
||||
set class_ns [set ${class_ns}::super] |
||||
if {[llength [info commands ${class_ns}::methods::_m_$methodname]]} { |
||||
return ${class_ns}::methods::_m_$methodname |
||||
} |
||||
} |
||||
|
||||
return ""; # Not found |
||||
} |
||||
|
||||
proc metoo::_new {class_ns cmd args} { |
||||
# class_ns expected to be fully qualified |
||||
variable next_id |
||||
|
||||
# IMPORTANT: |
||||
# object namespace *must* be child of class namespace. |
||||
# Saves a bit of bookkeeping. Putting it somewhere else will require |
||||
# changes to many other places in the code. |
||||
set objns ${class_ns}::o#[incr next_id] |
||||
|
||||
switch -exact -- $cmd { |
||||
create { |
||||
if {[llength $args] < 1} { |
||||
error "Insufficient args, should be: class create CLASSNAME ?args?" |
||||
} |
||||
# TBD - check if command already exists |
||||
# Note objname must always be fully qualified. Note cannot |
||||
# use namespace which here because the commmand does not |
||||
# yet exist. |
||||
set args [lassign $args objname] |
||||
if {[string compare :: [string range $objname 0 1]]} { |
||||
# Not fully qualified. Qualify based on caller namespace |
||||
set objname [uplevel 1 "namespace current"]::$objname |
||||
} |
||||
# Trip excess ":" - can happen in both above cases |
||||
set objname ::[string trimleft $objname :] |
||||
} |
||||
new { |
||||
set objname $objns |
||||
} |
||||
default { |
||||
error "Unknown command '$cmd'. Should be create or new." |
||||
} |
||||
} |
||||
|
||||
# Create the namespace. The array _ is used to hold private information |
||||
namespace eval $objns { |
||||
variable _ |
||||
} |
||||
set ${objns}::_(name) $objname |
||||
|
||||
# When invoked by its name, call the dispatcher. |
||||
interp alias {} $objname {} ${class_ns}::_call $objns |
||||
|
||||
# Register the object. We do this BEFORE running the constructor |
||||
variable _objects |
||||
set _objects($objname) $objns |
||||
|
||||
# Invoke the constructor |
||||
if {[catch { |
||||
$objname constructor {*}$args |
||||
} msg]} { |
||||
# Undo what we did |
||||
set erinfo $::errorInfo |
||||
set ercode $::errorCode |
||||
rename $objname "" |
||||
namespace delete $objns |
||||
error $msg $erinfo $ercode |
||||
} |
||||
|
||||
# TBD - does tracing cause a slowdown ? |
||||
# Set up trace to track when the object is renamed/destroyed |
||||
trace add command $objname {rename delete} [list [namespace current]::_trace_object_renames $objns] |
||||
|
||||
return $objname |
||||
} |
||||
|
||||
proc metoo::_trace_object_renames {objns oldname newname op} { |
||||
# Note the trace command fully qualifies oldname and newname |
||||
if {$op eq "rename"} { |
||||
variable _objects |
||||
set _objects($newname) $_objects($oldname) |
||||
unset _objects($oldname) |
||||
set ${objns}::_(name) $newname |
||||
} else { |
||||
$oldname destroy |
||||
} |
||||
} |
||||
|
||||
proc metoo::_class_cmd {class_ns cmd args} { |
||||
switch -exact -- $cmd { |
||||
create - |
||||
new { |
||||
return [uplevel 1 [list [namespace current]::_new $class_ns $cmd] $args] |
||||
} |
||||
destroy { |
||||
# Destroy all objects belonging to this class |
||||
foreach objns [namespace children ${class_ns} o#*] { |
||||
[set ${objns}::_(name)] destroy |
||||
} |
||||
# Destroy all classes that inherit from this |
||||
foreach child_ns [array names ${class_ns}::subclasses] { |
||||
# Child namespace is also subclass command |
||||
$child_ns destroy |
||||
} |
||||
trace remove command $class_ns {rename delete} [list ::metoo::_trace_class_renames] |
||||
namespace delete ${class_ns} |
||||
rename ${class_ns} "" |
||||
} |
||||
default { |
||||
error "Unknown command '$cmd'. Should be create, new or destroy." |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc metoo::class {cmd cname definition} { |
||||
variable next_id |
||||
|
||||
if {$cmd ne "create"} { |
||||
error "Syntax: class create CLASSNAME DEFINITION" |
||||
} |
||||
|
||||
if {[uplevel 1 "namespace exists $cname"]} { |
||||
error "can't create class '$cname': namespace already exists with that name." |
||||
} |
||||
|
||||
# Resolve cname into a namespace in the caller's context |
||||
set class_ns [uplevel 1 "namespace eval $cname {namespace current}"] |
||||
|
||||
if {[llength [info commands $class_ns]]} { |
||||
# Delete the namespace we just created |
||||
namespace delete $class_ns |
||||
error "can't create class '$cname': command already exists with that name." |
||||
} |
||||
|
||||
# Define the commands/aliases that are used inside a class definition |
||||
foreach procname [info commands [namespace current]::define::*] { |
||||
interp alias {} ${class_ns}::[namespace tail $procname] {} $procname $class_ns |
||||
} |
||||
|
||||
# Define the built in commands callable within class instance methods |
||||
foreach procname [info commands [namespace current]::object::*] { |
||||
interp alias {} ${class_ns}::methods::[namespace tail $procname] {} $procname |
||||
} |
||||
|
||||
# Define the destroy method for the class object instances |
||||
namespace eval $class_ns { |
||||
method destroy {} { |
||||
set retval [my destructor] |
||||
# Remove trace on command rename/deletion. |
||||
# ${_this}::_(name) contains the object's current name on |
||||
# which the trace is set. |
||||
set me [set ${_this}::_(name)] |
||||
trace remove command $me {rename delete} [list ::metoo::_trace_object_renames $_this] |
||||
rename $me "" |
||||
unset -nocomplain ::metoo::_objects($me) |
||||
namespace delete $_this |
||||
return $retval |
||||
} |
||||
method variable {args} { |
||||
if {[llength $args]} { |
||||
set cmd [list upvar 0] |
||||
foreach varname $args { |
||||
lappend cmd ${_this}::$varname $varname |
||||
} |
||||
uplevel 1 $cmd |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Define the class. Note we do this *after* the standard |
||||
# definitions (destroy etc.) above so that they can |
||||
# be overridden by the class definition. |
||||
if {[catch { |
||||
namespace eval $class_ns $definition |
||||
} msg ]} { |
||||
namespace delete $class_ns |
||||
error $msg $::errorInfo $::errorCode |
||||
} |
||||
|
||||
# Also define the call dispatcher within the class. |
||||
# TBD - not sure this is actually necessary any more |
||||
namespace eval ${class_ns} { |
||||
proc _call {objns methodname args} { |
||||
# Note this duplicates the "my" code but cannot call that as |
||||
# it adds another frame level which interferes with uplevel etc. |
||||
|
||||
set class_ns [namespace parent $objns] |
||||
|
||||
# We insert the object namespace as the first param to the command. |
||||
# This is passed as the first parameter "_this" to methods. |
||||
|
||||
set meth [::metoo::_locate_method $class_ns $methodname] |
||||
if {$meth ne ""} { |
||||
# We need to invoke in the caller's context so upvar etc. will |
||||
# not be affected by this intermediate method dispatcher |
||||
return [uplevel 1 [list $meth $objns] $args] |
||||
} |
||||
|
||||
# It is ok for constructor or destructor to be undefined. For |
||||
# the others, invoke "unknown" if it exists |
||||
|
||||
if {$methodname eq "constructor" || $methodname eq "destructor"} { |
||||
return |
||||
} |
||||
|
||||
set meth [::metoo::_locate_method $class_ns "unknown"] |
||||
if {$meth ne ""} { |
||||
# We need to invoke in the caller's context so upvar etc. will |
||||
# not be affected by this intermediate method dispatcher |
||||
return [uplevel 1 [list $meth $objns $methodname] $args] |
||||
} |
||||
|
||||
error "Unknown method $methodname" |
||||
} |
||||
} |
||||
|
||||
# The namespace is also a command used to create class instances |
||||
# TBD - check if command of that name already exists |
||||
interp alias {} $class_ns {} [namespace current]::_class_cmd $class_ns |
||||
# Set up trace to track when the class command is renamed/destroyed |
||||
trace add command $class_ns [list rename delete] ::metoo::_trace_class_renames |
||||
|
||||
return $class_ns |
||||
} |
||||
|
||||
proc metoo::_trace_class_renames {oldname newname op} { |
||||
if {$op eq "rename"} { |
||||
# TBD - this does not actually work. The rename succeeds anyways |
||||
error "MetOO classes may not be renamed" |
||||
} else { |
||||
$oldname destroy |
||||
} |
||||
} |
||||
|
||||
proc metoo::introspect {type info args} { |
||||
switch -exact -- $type { |
||||
"object" { |
||||
variable _objects |
||||
switch -exact -- $info { |
||||
"isa" { |
||||
if {[llength $args] == 0 || [llength $args] > 2} { |
||||
error "wrong # args: should be \"metoo::introspect $type $info OBJNAME ?CLASS?\"" |
||||
} |
||||
set objname [uplevel 1 [list namespace which -command [lindex $args 0]]] |
||||
if {![info exists _objects($objname)]} { |
||||
return 0 |
||||
} |
||||
if {[llength $args] == 1} { |
||||
# No class specified |
||||
return 1 |
||||
} |
||||
# passed classname assumed to be fully qualified |
||||
set objclass [namespace parent $_objects($objname)] |
||||
if {[string equal $objclass [lindex $args 1]]} { |
||||
# Direct hit |
||||
return 1 |
||||
} |
||||
|
||||
# No direct hit, check ancestors |
||||
if {[lindex $args 1] in [ancestors $objclass]} { |
||||
return 1 |
||||
} |
||||
|
||||
return 0 |
||||
} |
||||
|
||||
"list" { |
||||
if {[llength $args] > 1} { |
||||
error "wrong # args: should be \"metoo::introspect $type $info ?CLASS?" |
||||
} |
||||
variable _objects |
||||
if {[llength $args] == 0} { |
||||
return [array names _objects] |
||||
} |
||||
set objs {} |
||||
foreach obj [array names _objects] { |
||||
if {[introspect object isa $obj [lindex $args 0]]} { |
||||
lappend objs $obj |
||||
} |
||||
} |
||||
return $objs |
||||
} |
||||
default { |
||||
error "$info subcommand not supported for $type introspection" |
||||
} |
||||
} |
||||
} |
||||
|
||||
"class" { |
||||
switch -exact -- $info { |
||||
"ancestors" { |
||||
if {[llength $args] != 1} { |
||||
error "wrong # args: should be \"metoo::introspect $type $info CLASSNAME" |
||||
} |
||||
return [ancestors [lindex $args 0]] |
||||
} |
||||
default { |
||||
error "$info subcommand not supported for $type introspection" |
||||
} |
||||
} |
||||
} |
||||
default { |
||||
error "$type introspection not supported" |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc metoo::ancestors {class_ns} { |
||||
# Returns ancestors of a class |
||||
|
||||
set ancestors [list ] |
||||
while {[info exists ${class_ns}::super]} { |
||||
lappend ancestors [set class_ns [set ${class_ns}::super]] |
||||
} |
||||
|
||||
return $ancestors |
||||
} |
||||
|
||||
namespace eval metoo { namespace export class } |
||||
|
||||
# Simple sample class showing all capabilities. Anything not shown here will |
||||
# probably not work. Call as "demo" to use metoo, or "demo oo" to use TclOO. |
||||
# Output should be same in both cases. |
||||
proc ::metoo::demo {{ns metoo}} { |
||||
${ns}::class create Base { |
||||
constructor {x y} { puts "Base constructor ([self object]): $x, $y" |
||||
} |
||||
method m {} { puts "Base::m called" } |
||||
method n {args} { puts "Base::n called: [join $args {, }]"; my m } |
||||
method unknown {methodname args} { puts "Base::unknown called for $methodname [join $args {, }]"} |
||||
destructor { puts "Base::destructor ([self object])" } |
||||
} |
||||
|
||||
${ns}::class create Derived { |
||||
superclass Base |
||||
constructor {x y} { puts "Derived constructor ([self object]): $x, $y" ; next $x $y } |
||||
destructor { puts "Derived::destructor called ([self object])" ; next } |
||||
method n {args} { puts "Derived::n ([self object]): [join $args {, }]"; next {*}$args} |
||||
method put {val} {my variable var ; set var $val} |
||||
method get {varname} {my variable var ; upvar 1 $varname retvar; set retvar $var} |
||||
} |
||||
|
||||
Base create b dum dee; # Create named object |
||||
Derived create d fee fi; # Create derived object |
||||
set o [Derived new fo fum]; # Create autonamed object |
||||
$o put 10; # Use of instance variable |
||||
$o get v; # Verify correct frame level ... |
||||
puts "v:$v"; # ...when calling methods |
||||
b m; # Direct method |
||||
b n; # Use of my to call another method |
||||
$o m; # Inherited method |
||||
$o n; # Overridden method chained to inherited |
||||
$o nosuchmethod arg1 arg2; # Invoke unknown |
||||
$o destroy; # Explicit destroy |
||||
rename b ""; # Destroy through rename |
||||
Base destroy; # Should destroy object d, Derived, Base |
||||
} |
||||
|
||||
# Hack to work with the various build configuration. |
||||
if {[info commands ::twapi::get_version] ne ""} { |
||||
package provide metoo [::twapi::get_version -patchlevel] |
||||
} |
@ -0,0 +1,403 @@
|
||||
# |
||||
# Copyright (c) 2003-2018, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Hack to work with the various build configuration. |
||||
if {[info commands ::twapi::get_version] ne ""} { |
||||
package provide twapi_msi [::twapi::get_version -patchlevel] |
||||
} |
||||
|
||||
# Rest of this file auto-generated |
||||
|
||||
|
||||
# Automatically generated type library interface |
||||
# File: msi.dll |
||||
# Name: WindowsInstaller |
||||
# GUID: {000C1092-0000-0000-C000-000000000046} |
||||
# Version: 1.0 |
||||
# LCID: 1033 |
||||
package require twapi_com |
||||
|
||||
namespace eval windowsinstaller { |
||||
|
||||
# Array mapping coclass names to their guids |
||||
variable _coclass_guids |
||||
|
||||
# Array mapping dispatch interface names to their guids |
||||
variable _dispatch_guids |
||||
|
||||
# Returns the GUID for a coclass or empty string if not found |
||||
proc coclass_guid {coclass_name} { |
||||
variable _coclass_guids |
||||
if {[info exists _coclass_guids($coclass_name)]} { |
||||
return $_coclass_guids($coclass_name) |
||||
} |
||||
return "" |
||||
} |
||||
# Returns the GUID for a dispatch name or empty string if not found |
||||
proc dispatch_guid {dispatch_name} { |
||||
variable _dispatch_guids |
||||
if {[info exists _dispatch_guids($dispatch_name)]} { |
||||
return $_dispatch_guids($dispatch_name) |
||||
} |
||||
return "" |
||||
} |
||||
# Marks the specified object to be of a specific dispatch/coclass type |
||||
proc declare {typename comobj} { |
||||
# First check if it is the name of a dispatch interface |
||||
set guid [dispatch_guid $typename] |
||||
if {$guid ne ""} { |
||||
$comobj -interfaceguid $guid |
||||
return |
||||
} |
||||
|
||||
# If not, check if it is the name of a coclass with a dispatch interface |
||||
set guid [coclass_guid $typename] |
||||
if {$guid ne ""} { |
||||
if {[info exists ::twapi::_coclass_idispatch_guids($guid)]} { |
||||
$comobj -interfaceguid $::twapi::_coclass_idispatch_guids($guid) |
||||
return |
||||
} |
||||
} |
||||
|
||||
error "Could not resolve interface for $coclass_name." |
||||
} |
||||
|
||||
# Enum MsiUILevel |
||||
array set MsiUILevel {msiUILevelNoChange 0 msiUILevelDefault 1 msiUILevelNone 2 msiUILevelBasic 3 msiUILevelReduced 4 msiUILevelFull 5 msiUILevelHideCancel 32 msiUILevelProgressOnly 64 msiUILevelEndDialog 128 msiUILevelSourceResOnly 256} |
||||
|
||||
# Enum MsiReadStream |
||||
array set MsiReadStream {msiReadStreamInteger 0 msiReadStreamBytes 1 msiReadStreamAnsi 2 msiReadStreamDirect 3} |
||||
|
||||
# Enum MsiRunMode |
||||
array set MsiRunMode {msiRunModeAdmin 0 msiRunModeAdvertise 1 msiRunModeMaintenance 2 msiRunModeRollbackEnabled 3 msiRunModeLogEnabled 4 msiRunModeOperations 5 msiRunModeRebootAtEnd 6 msiRunModeRebootNow 7 msiRunModeCabinet 8 msiRunModeSourceShortNames 9 msiRunModeTargetShortNames 10 msiRunModeWindows9x 12 msiRunModeZawEnabled 13 msiRunModeScheduled 16 msiRunModeRollback 17 msiRunModeCommit 18} |
||||
|
||||
# Enum MsiDatabaseState |
||||
array set MsiDatabaseState {msiDatabaseStateRead 0 msiDatabaseStateWrite 1} |
||||
|
||||
# Enum MsiViewModify |
||||
array set MsiViewModify {msiViewModifySeek -1 msiViewModifyRefresh 0 msiViewModifyInsert 1 msiViewModifyUpdate 2 msiViewModifyAssign 3 msiViewModifyReplace 4 msiViewModifyMerge 5 msiViewModifyDelete 6 msiViewModifyInsertTemporary 7 msiViewModifyValidate 8 msiViewModifyValidateNew 9 msiViewModifyValidateField 10 msiViewModifyValidateDelete 11} |
||||
|
||||
# Enum MsiColumnInfo |
||||
array set MsiColumnInfo {msiColumnInfoNames 0 msiColumnInfoTypes 1} |
||||
|
||||
# Enum MsiTransformError |
||||
array set MsiTransformError {msiTransformErrorNone 0 msiTransformErrorAddExistingRow 1 msiTransformErrorDeleteNonExistingRow 2 msiTransformErrorAddExistingTable 4 msiTransformErrorDeleteNonExistingTable 8 msiTransformErrorUpdateNonExistingRow 16 msiTransformErrorChangeCodePage 32 msiTransformErrorViewTransform 256} |
||||
|
||||
# Enum MsiEvaluateCondition |
||||
array set MsiEvaluateCondition {msiEvaluateConditionFalse 0 msiEvaluateConditionTrue 1 msiEvaluateConditionNone 2 msiEvaluateConditionError 3} |
||||
|
||||
# Enum MsiTransformValidation |
||||
array set MsiTransformValidation {msiTransformValidationNone 0 msiTransformValidationLanguage 1 msiTransformValidationProduct 2 msiTransformValidationPlatform 4 msiTransformValidationMajorVer 8 msiTransformValidationMinorVer 16 msiTransformValidationUpdateVer 32 msiTransformValidationLess 64 msiTransformValidationLessOrEqual 128 msiTransformValidationEqual 256 msiTransformValidationGreaterOrEqual 512 msiTransformValidationGreater 1024 msiTransformValidationUpgradeCode 2048} |
||||
|
||||
# Enum MsiDoActionStatus |
||||
array set MsiDoActionStatus {msiDoActionStatusNoAction 0 msiDoActionStatusSuccess 1 msiDoActionStatusUserExit 2 msiDoActionStatusFailure 3 msiDoActionStatusSuspend 4 msiDoActionStatusFinished 5 msiDoActionStatusWrongState 6 msiDoActionStatusBadActionData 7} |
||||
|
||||
# Enum MsiMessageStatus |
||||
array set MsiMessageStatus {msiMessageStatusError -1 msiMessageStatusNone 0 msiMessageStatusOk 1 msiMessageStatusCancel 2 msiMessageStatusAbort 3 msiMessageStatusRetry 4 msiMessageStatusIgnore 5 msiMessageStatusYes 6 msiMessageStatusNo 7} |
||||
|
||||
# Enum MsiMessageType |
||||
array set MsiMessageType {msiMessageTypeFatalExit 0 msiMessageTypeError 16777216 msiMessageTypeWarning 33554432 msiMessageTypeUser 50331648 msiMessageTypeInfo 67108864 msiMessageTypeFilesInUse 83886080 msiMessageTypeResolveSource 100663296 msiMessageTypeOutOfDiskSpace 117440512 msiMessageTypeActionStart 134217728 msiMessageTypeActionData 150994944 msiMessageTypeProgress 167772160 msiMessageTypeCommonData 184549376 msiMessageTypeOk 0 msiMessageTypeOkCancel 1 msiMessageTypeAbortRetryIgnore 2 msiMessageTypeYesNoCancel 3 msiMessageTypeYesNo 4 msiMessageTypeRetryCancel 5 msiMessageTypeDefault1 0 msiMessageTypeDefault2 256 msiMessageTypeDefault3 512} |
||||
|
||||
# Enum MsiInstallState |
||||
array set MsiInstallState {msiInstallStateNotUsed -7 msiInstallStateBadConfig -6 msiInstallStateIncomplete -5 msiInstallStateSourceAbsent -4 msiInstallStateInvalidArg -2 msiInstallStateUnknown -1 msiInstallStateBroken 0 msiInstallStateAdvertised 1 msiInstallStateRemoved 1 msiInstallStateAbsent 2 msiInstallStateLocal 3 msiInstallStateSource 4 msiInstallStateDefault 5} |
||||
|
||||
# Enum MsiCostTree |
||||
array set MsiCostTree {msiCostTreeSelfOnly 0 msiCostTreeChildren 1 msiCostTreeParents 2} |
||||
|
||||
# Enum MsiReinstallMode |
||||
array set MsiReinstallMode {msiReinstallModeFileMissing 2 msiReinstallModeFileOlderVersion 4 msiReinstallModeFileEqualVersion 8 msiReinstallModeFileExact 16 msiReinstallModeFileVerify 32 msiReinstallModeFileReplace 64 msiReinstallModeMachineData 128 msiReinstallModeUserData 256 msiReinstallModeShortcut 512 msiReinstallModePackage 1024} |
||||
|
||||
# Enum MsiInstallType |
||||
array set MsiInstallType {msiInstallTypeDefault 0 msiInstallTypeNetworkImage 1 msiInstallTypeSingleInstance 2} |
||||
|
||||
# Enum MsiInstallMode |
||||
array set MsiInstallMode {msiInstallModeNoSourceResolution -3 msiInstallModeNoDetection -2 msiInstallModeExisting -1 msiInstallModeDefault 0} |
||||
|
||||
# Enum MsiSignatureInfo |
||||
array set MsiSignatureInfo {msiSignatureInfoCertificate 0 msiSignatureInfoHash 1} |
||||
|
||||
# Enum MsiInstallContext |
||||
array set MsiInstallContext {msiInstallContextFirstVisible 0 msiInstallContextUserManaged 1 msiInstallContextUser 2 msiInstallContextMachine 4 msiInstallContextAllUserManaged 8} |
||||
|
||||
# Enum MsiInstallSourceType |
||||
array set MsiInstallSourceType {msiInstallSourceTypeUnknown 0 msiInstallSourceTypeNetwork 1 msiInstallSourceTypeURL 2 msiInstallSourceTypeMedia 4} |
||||
|
||||
# Enum MsiAssemblyType |
||||
array set MsiAssemblyType {msiProvideAssemblyNet 0 msiProvideAssemblyWin32 1} |
||||
|
||||
# Enum MsiProductScriptInfo |
||||
array set MsiProductScriptInfo {msiProductScriptInfoProductCode 0 msiProductScriptInfoProductLanguage 1 msiProductScriptInfoProductVersion 2 msiProductScriptInfoProductName 3 msiProductScriptInfoPackageName 4} |
||||
|
||||
# Enum MsiAdvertiseProductContext |
||||
array set MsiAdvertiseProductContext {msiAdvertiseProductMachine 0 msiAdvertiseProductUser 1} |
||||
|
||||
# Enum Constants |
||||
array set Constants {msiDatabaseNullInteger -2147483648} |
||||
|
||||
# Enum MsiOpenDatabaseMode |
||||
array set MsiOpenDatabaseMode {msiOpenDatabaseModeReadOnly 0 msiOpenDatabaseModeTransact 1 msiOpenDatabaseModeDirect 2 msiOpenDatabaseModeCreate 3 msiOpenDatabaseModeCreateDirect 4 msiOpenDatabaseModePatchFile 32} |
||||
|
||||
# Enum MsiSignatureOption |
||||
array set MsiSignatureOption {msiSignatureOptionInvalidHashFatal 1} |
||||
|
||||
# Enum MsiAdvertiseProductPlatform |
||||
array set MsiAdvertiseProductPlatform {msiAdvertiseCurrentPlatform 0 msiAdvertiseX86Platform 1 msiAdvertiseIA64Platform 2 msiAdvertiseX64Platform 4} |
||||
|
||||
# Enum MsiAdvertiseProductOptions |
||||
array set MsiAdvertiseProductOptions {msiAdvertiseDefault 0 msiAdvertiseSingleInstance 1} |
||||
|
||||
# Enum MsiAdvertiseScriptFlags |
||||
array set MsiAdvertiseScriptFlags {msiAdvertiseScriptCacheInfo 1 msiAdvertiseScriptShortcuts 4 msiAdvertiseScriptMachineAssign 8 msiAdvertiseScriptConfigurationRegistration 32 msiAdvertiseScriptValidateTransformsList 64 msiAdvertiseScriptClassInfoRegistration 128 msiAdvertiseScriptExtensionInfoRegistration 256 msiAdvertiseScriptAppInfo 384 msiAdvertiseScriptRegData 416} |
||||
} |
||||
|
||||
# Dispatch Interface Installer |
||||
set windowsinstaller::_dispatch_guids(Installer) "{000C1090-0000-0000-C000-000000000046}" |
||||
# Installer Methods |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CreateRecord 1033 1 {1 1033 1 {26 {29 256}} {{3 1}} Count} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenPackage 1033 1 {2 1033 1 {26 {29 512}} {{12 1} {3 {49 {3 0}}}} {PackagePath Options}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenProduct 1033 1 {3 1033 1 {26 {29 512}} {{8 1}} ProductCode} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenDatabase 1033 1 {4 1033 1 {26 {29 768}} {{8 1} {12 1}} {DatabasePath OpenMode}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} SummaryInformation 1033 2 {5 1033 2 {26 {29 1024}} {{8 1} {3 {49 {3 0}}}} {PackagePath UpdateCount}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} EnableLog 1033 1 {7 1033 1 24 {{8 1} {8 1}} {LogMode LogFile}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} InstallProduct 1033 1 {8 1033 1 24 {{8 1} {8 {49 {8 0}}}} {PackagePath PropertyValues}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Version 1033 2 {9 1033 2 8 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} LastErrorRecord 1033 1 {10 1033 1 {26 {29 256}} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RegistryValue 1033 1 {11 1033 1 8 {{12 1} {8 1} {12 17}} {Root Key Value}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileAttributes 1033 1 {13 1033 1 3 {{8 1}} FilePath} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileSize 1033 1 {15 1033 1 3 {{8 1}} FilePath} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileVersion 1033 1 {16 1033 1 8 {{8 1} {12 17}} {FilePath Language}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Environment 1033 2 {12 1033 2 8 {{8 1}} Variable} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Environment 1033 4 {12 1033 4 24 {{8 1} {8 1}} Variable} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductState 1033 2 {17 1033 2 {29 2432} {{8 1}} Product} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductInfo 1033 2 {18 1033 2 8 {{8 1} {8 1}} {Product Attribute}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ConfigureProduct 1033 1 {19 1033 1 24 {{8 1} {3 1} {3 1}} {Product InstallLevel InstallState}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ReinstallProduct 1033 1 {20 1033 1 24 {{8 1} {3 1}} {Product ReinstallMode}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CollectUserInfo 1033 1 {21 1033 1 24 {{8 1}} Product} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ApplyPatch 1033 1 {22 1033 1 24 {{8 1} {8 1} {3 1} {8 1}} {PatchPackage InstallPackage InstallType CommandLine}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureParent 1033 2 {23 1033 2 8 {{8 1} {8 1}} {Product Feature}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureState 1033 2 {24 1033 2 {29 2432} {{8 1} {8 1}} {Product Feature}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UseFeature 1033 1 {25 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature InstallMode}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureUsageCount 1033 2 {26 1033 2 3 {{8 1} {8 1}} {Product Feature}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureUsageDate 1033 2 {27 1033 2 7 {{8 1} {8 1}} {Product Feature}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ConfigureFeature 1033 1 {28 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature InstallState}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ReinstallFeature 1033 1 {29 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature ReinstallMode}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideComponent 1033 1 {30 1033 1 8 {{8 1} {8 1} {8 1} {3 1}} {Product Feature Component InstallMode}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentPath 1033 2 {31 1033 2 8 {{8 1} {8 1}} {Product Component}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideQualifiedComponent 1033 1 {32 1033 1 8 {{8 1} {8 1} {3 1}} {Category Qualifier InstallMode}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} QualifierDescription 1033 2 {33 1033 2 8 {{8 1} {8 1}} {Category Qualifier}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentQualifiers 1033 2 {34 1033 2 {26 {29 3328}} {{8 1}} Category} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Products 1033 2 {35 1033 2 {26 {29 3328}} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Features 1033 2 {36 1033 2 {26 {29 3328}} {{8 1}} Product} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Components 1033 2 {37 1033 2 {26 {29 3328}} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentClients 1033 2 {38 1033 2 {26 {29 3328}} {{8 1}} Component} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Patches 1033 2 {39 1033 2 {26 {29 3328}} {{8 1}} Product} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RelatedProducts 1033 2 {40 1033 2 {26 {29 3328}} {{8 1}} UpgradeCode} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchInfo 1033 2 {41 1033 2 8 {{8 1} {8 1}} {Patch Attribute}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchTransforms 1033 2 {42 1033 2 8 {{8 1} {8 1}} {Product Patch}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AddSource 1033 1 {43 1033 1 24 {{8 1} {8 1} {8 1}} {Product User Source}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ClearSourceList 1033 1 {44 1033 1 24 {{8 1} {8 1}} {Product User}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ForceSourceListResolution 1033 1 {45 1033 1 24 {{8 1} {8 1}} {Product User}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} GetShortcutTarget 1033 2 {46 1033 2 {26 {29 256}} {{8 1}} ShortcutPath} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileHash 1033 1 {47 1033 1 {26 {29 256}} {{8 1} {3 1}} {FilePath Options}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileSignatureInfo 1033 1 {48 1033 1 {27 17} {{8 1} {3 1} {3 1}} {FilePath Options Format}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RemovePatches 1033 1 {49 1033 1 24 {{8 1} {8 1} {3 1} {8 {49 {8 0}}}} {PatchList Product UninstallType PropertyList}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ApplyMultiplePatches 1033 1 {51 1033 1 24 {{8 1} {8 1} {8 1}} {PatchPackage Product PropertiesList}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Product 1033 2 {53 1033 2 25 {{8 1} {8 1} {3 1} {{26 9} 10}} {Product UserSid iContext retval}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Patch 1033 2 {56 1033 2 25 {{8 1} {8 1} {8 1} {3 1} {{26 9} 10}} {PatchCode ProductCode UserSid iContext retval}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductsEx 1033 2 {52 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1}} {Product UserSid Contexts}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchesEx 1033 2 {55 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1} {3 1}} {Product UserSid Contexts filter}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ExtractPatchXMLData 1033 1 {57 1033 1 8 {{8 1}} PatchPath} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductCode 1033 2 {58 1033 2 8 {{8 1}} Component} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductElevated 1033 2 {59 1033 2 11 {{8 1}} Product} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideAssembly 1033 1 {60 1033 1 8 {{8 1} {8 1} {3 1} {3 1}} {Assembly Context InstallMode AssemblyInfo}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductInfoFromScript 1033 2 {61 1033 2 12 {{8 1} {3 1}} {ScriptFile ProductInfo}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AdvertiseProduct 1033 1 {62 1033 1 24 {{8 1} {3 1} {8 {49 {8 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}}} {PackagePath iContext Transforms Language Options}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CreateAdvertiseScript 1033 1 {63 1033 1 24 {{8 1} {8 1} {8 {49 {8 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}}} {PackagePath ScriptFilePath Transforms Language Platform Options}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AdvertiseScript 1033 1 {64 1033 1 24 {{8 1} {3 1} {11 1}} {ScriptPath ScriptFlags RemoveItems}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchFiles 1033 2 {65 1033 2 {26 {29 3328}} {{8 1} {8 1}} {Product PatchPackages}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentsEx 1033 2 {66 1033 2 {26 {29 2816}} {{8 1} {3 1}} {UserSid Context}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentClientsEx 1033 2 {67 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1}} {ComponentCode UserSid Context}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentPathEx 1033 2 {9068 1033 2 {26 {29 4480}} {{8 1} {8 1} {8 1} {3 1}} {ProductCode ComponentCode UserSid Context}} |
||||
# Installer Properties |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UILevel 1033 2 {6 1033 2 {29 128} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UILevel 1033 4 {6 1033 4 24 {{{29 128} 1}} {}} |
||||
|
||||
# Dispatch Interface Record |
||||
set windowsinstaller::_dispatch_guids(Record) "{000C1093-0000-0000-C000-000000000046}" |
||||
# Record Methods |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} StringData 1033 2 {1 1033 2 8 {{3 1}} Field} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} StringData 1033 4 {1 1033 4 24 {{3 1} {8 1}} Field} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IntegerData 1033 2 {2 1033 2 3 {{3 1}} Field} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IntegerData 1033 4 {2 1033 4 24 {{3 1} {3 1}} Field} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} SetStream 1033 1 {3 1033 1 24 {{3 1} {8 1}} {Field FilePath}} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} ReadStream 1033 1 {4 1033 1 8 {{3 1} {3 1} {3 1}} {Field Length Format}} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} FieldCount 1033 2 {0 1033 2 3 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IsNull 1033 2 {6 1033 2 11 {{3 1}} Field} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} DataSize 1033 2 {5 1033 2 3 {{3 1}} Field} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} ClearData 1033 1 {7 1033 1 24 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} FormatText 1033 1 {8 1033 1 8 {} {}} |
||||
|
||||
# Dispatch Interface Session |
||||
set windowsinstaller::_dispatch_guids(Session) "{000C109E-0000-0000-C000-000000000046}" |
||||
# Session Methods |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Installer 1033 2 {1 1033 2 {26 {29 0}} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Property 1033 2 {2 1033 2 8 {{8 1}} Name} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Property 1033 4 {2 1033 4 24 {{8 1} {8 1}} Name} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Language 1033 2 {3 1033 2 3 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Mode 1033 2 {4 1033 2 11 {{3 1}} Flag} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Mode 1033 4 {4 1033 4 24 {{3 1} {11 1}} Flag} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Database 1033 2 {5 1033 2 {26 {29 768}} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} SourcePath 1033 2 {6 1033 2 8 {{8 1}} Folder} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} TargetPath 1033 2 {7 1033 2 8 {{8 1}} Folder} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} TargetPath 1033 4 {7 1033 4 24 {{8 1} {8 1}} Folder} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} DoAction 1033 1 {8 1033 1 {29 2048} {{8 1}} Action} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Sequence 1033 1 {9 1033 1 {29 2048} {{8 1} {12 17}} {Table Mode}} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} EvaluateCondition 1033 1 {10 1033 1 {29 1792} {{8 1}} Expression} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FormatRecord 1033 1 {11 1033 1 8 {{9 1}} Record} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Message 1033 1 {12 1033 1 {29 2176} {{3 1} {9 1}} {Kind Record}} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureCurrentState 1033 2 {13 1033 2 {29 2432} {{8 1}} Feature} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureRequestState 1033 2 {14 1033 2 {29 2432} {{8 1}} Feature} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureRequestState 1033 4 {14 1033 4 24 {{8 1} {3 1}} Feature} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureValidStates 1033 2 {15 1033 2 3 {{8 1}} Feature} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureCost 1033 2 {16 1033 2 3 {{8 1} {3 1} {3 1}} {Feature CostTree State}} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentCurrentState 1033 2 {17 1033 2 {29 2432} {{8 1}} Component} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentRequestState 1033 2 {18 1033 2 {29 2432} {{8 1}} Component} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentRequestState 1033 4 {18 1033 4 24 {{8 1} {3 1}} Component} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} SetInstallLevel 1033 1 {19 1033 1 24 {{3 1}} Level} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} VerifyDiskSpace 1033 2 {20 1033 2 11 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ProductProperty 1033 2 {21 1033 2 8 {{8 1}} Property} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureInfo 1033 2 {22 1033 2 {26 {29 2688}} {{8 1}} Feature} |
||||
::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentCosts 1033 2 {23 1033 2 {26 {29 2816}} {{8 1} {3 1}} {Component State}} |
||||
|
||||
# Dispatch Interface Database |
||||
set windowsinstaller::_dispatch_guids(Database) "{000C109D-0000-0000-C000-000000000046}" |
||||
# Database Methods |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} DatabaseState 1033 2 {1 1033 2 {29 896} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} SummaryInformation 1033 2 {2 1033 2 {26 {29 1024}} {{3 {49 {3 0}}}} UpdateCount} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} OpenView 1033 1 {3 1033 1 {26 {29 1152}} {{8 1}} Sql} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Commit 1033 1 {4 1033 1 24 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} PrimaryKeys 1033 2 {5 1033 2 {26 {29 256}} {{8 1}} Table} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Import 1033 1 {6 1033 1 24 {{8 1} {8 1}} {Folder File}} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Export 1033 1 {7 1033 1 24 {{8 1} {8 1} {8 1}} {Table Folder File}} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Merge 1033 1 {8 1033 1 11 {{9 1} {8 {49 {8 0}}}} {Database ErrorTable}} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} GenerateTransform 1033 1 {9 1033 1 11 {{9 1} {8 {49 {8 0}}}} {ReferenceDatabase TransformFile}} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} ApplyTransform 1033 1 {10 1033 1 24 {{8 1} {3 1}} {TransformFile ErrorConditions}} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} EnableUIPreview 1033 1 {11 1033 1 {26 {29 1664}} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} TablePersistent 1033 2 {12 1033 2 {29 1792} {{8 1}} Table} |
||||
::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} CreateTransformSummaryInfo 1033 1 {13 1033 1 24 {{9 1} {8 1} {3 1} {3 1}} {ReferenceDatabase TransformFile ErrorConditions Validation}} |
||||
|
||||
# Dispatch Interface SummaryInfo |
||||
set windowsinstaller::_dispatch_guids(SummaryInfo) "{000C109B-0000-0000-C000-000000000046}" |
||||
# SummaryInfo Methods |
||||
::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Property 1033 2 {1 1033 2 12 {{3 1}} Pid} |
||||
::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Property 1033 4 {1 1033 4 24 {{3 1} {12 1}} Pid} |
||||
::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} PropertyCount 1033 2 {2 1033 2 3 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Persist 1033 1 {3 1033 1 24 {} {}} |
||||
|
||||
# Dispatch Interface View |
||||
set windowsinstaller::_dispatch_guids(View) "{000C109C-0000-0000-C000-000000000046}" |
||||
# View Methods |
||||
::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Execute 1033 1 {1 1033 1 24 {{9 {49 {3 0}}}} Params} |
||||
::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Fetch 1033 1 {2 1033 1 {26 {29 256}} {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Modify 1033 1 {3 1033 1 24 {{3 1} {9 0}} {Mode Record}} |
||||
::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} ColumnInfo 1033 2 {5 1033 2 {26 {29 256}} {{3 1}} Info} |
||||
::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Close 1033 1 {4 1033 1 24 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} GetError 1033 1 {6 1033 1 8 {} {}} |
||||
|
||||
# Dispatch Interface UIPreview |
||||
set windowsinstaller::_dispatch_guids(UIPreview) "{000C109A-0000-0000-C000-000000000046}" |
||||
# UIPreview Methods |
||||
::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} Property 1033 2 {1 1033 2 8 {{8 1}} Name} |
||||
::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} Property 1033 4 {1 1033 4 24 {{8 1} {8 1}} Name} |
||||
::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} ViewDialog 1033 1 {2 1033 1 24 {{8 1}} Dialog} |
||||
::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} ViewBillboard 1033 1 {3 1033 1 24 {{8 1} {8 1}} {Control Billboard}} |
||||
|
||||
# Dispatch Interface FeatureInfo |
||||
set windowsinstaller::_dispatch_guids(FeatureInfo) "{000C109F-0000-0000-C000-000000000046}" |
||||
# FeatureInfo Methods |
||||
::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Title 1033 2 {1 1033 2 8 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Description 1033 2 {2 1033 2 8 {} {}} |
||||
# FeatureInfo Properties |
||||
::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Attributes 1033 2 {3 1033 2 3 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Attributes 1033 4 {3 1033 4 24 {{3 1}} {}} |
||||
|
||||
# Dispatch Interface RecordList |
||||
set windowsinstaller::_dispatch_guids(RecordList) "{000C1096-0000-0000-C000-000000000046}" |
||||
# RecordList Methods |
||||
::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} _NewEnum 1033 1 {-4 1033 1 13 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} Item 1033 2 {0 1033 2 {26 {29 256}} {{3 0}} Index} |
||||
::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} Count 1033 2 {1 1033 2 3 {} {}} |
||||
|
||||
# Dispatch Interface StringList |
||||
set windowsinstaller::_dispatch_guids(StringList) "{000C1095-0000-0000-C000-000000000046}" |
||||
# StringList Methods |
||||
::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} _NewEnum 1033 1 {-4 1033 1 13 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} Item 1033 2 {0 1033 2 8 {{3 0}} Index} |
||||
::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} Count 1033 2 {1 1033 2 3 {} {}} |
||||
|
||||
# Dispatch Interface Product |
||||
set windowsinstaller::_dispatch_guids(Product) "{000C10A0-0000-0000-C000-000000000046}" |
||||
# Product Methods |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} ProductCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} UserSid 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} Context 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} State 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} InstallProperty 1033 2 {5 1033 2 25 {{8 1} {{26 8} 10}} {Name retval}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} ComponentState 1033 2 {6 1033 2 25 {{8 1} {{26 3} 10}} {Component retval}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} FeatureState 1033 2 {7 1033 2 25 {{8 1} {{26 3} 10}} {Feature retval}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} Sources 1033 2 {14 1033 2 25 {{3 1} {{26 9} 10}} {SourceType retval}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} MediaDisks 1033 2 {15 1033 2 25 {{{26 9} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListAddSource 1033 1 {8 1033 1 25 {{3 1} {8 1} {3 1}} {iSourceType Source dwIndex}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListAddMediaDisk 1033 1 {9 1033 1 25 {{3 1} {8 1} {8 1}} {dwDiskId VolumeLabel DiskPrompt}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearSource 1033 1 {10 1033 1 25 {{3 1} {8 1}} {iSourceType Source}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearMediaDisk 1033 1 {11 1033 1 25 {{3 1}} iDiskId} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearAll 1033 1 {12 1033 1 25 {{3 1}} iSourceType} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListForceResolution 1033 1 {13 1033 1 25 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListInfo 1033 2 {16 1033 2 25 {{8 1} {{26 8} 10}} {Property retval}} |
||||
::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListInfo 1033 4 {16 1033 4 25 {{8 1} {8 1}} {Property retval}} |
||||
|
||||
# Dispatch Interface Patch |
||||
set windowsinstaller::_dispatch_guids(Patch) "{000C10A1-0000-0000-C000-000000000046}" |
||||
# Patch Methods |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} PatchCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} ProductCode 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} UserSid 1033 2 {3 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} Context 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} State 1033 2 {5 1033 2 25 {{{26 3} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} Sources 1033 2 {12 1033 2 25 {{3 1} {{26 9} 10}} {SourceType retval}} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} MediaDisks 1033 2 {13 1033 2 25 {{{26 9} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListAddSource 1033 1 {6 1033 1 25 {{3 1} {8 1} {3 1}} {iSourceType Source dwIndex}} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListAddMediaDisk 1033 1 {7 1033 1 25 {{3 1} {8 1} {8 1}} {dwDiskId VolumeLabel DiskPrompt}} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearSource 1033 1 {8 1033 1 25 {{3 1} {8 1}} {iSourceType Source}} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearMediaDisk 1033 1 {9 1033 1 25 {{3 1}} iDiskId} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearAll 1033 1 {10 1033 1 25 {{3 1}} iSourceType} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListForceResolution 1033 1 {11 1033 1 25 {} {}} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListInfo 1033 2 {14 1033 2 25 {{8 1} {{26 8} 10}} {Property retval}} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListInfo 1033 4 {14 1033 4 25 {{8 1} {8 1}} {Property retval}} |
||||
::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} PatchProperty 1033 2 {15 1033 2 25 {{8 1} {{26 8} 10}} {Property Value}} |
||||
|
||||
# Dispatch Interface ComponentPath |
||||
set windowsinstaller::_dispatch_guids(ComponentPath) "{000C1099-0000-0000-C000-000000000046}" |
||||
# ComponentPath Methods |
||||
::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} Path 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} State 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} |
||||
|
||||
# Dispatch Interface Component |
||||
set windowsinstaller::_dispatch_guids(Component) "{000C1097-0000-0000-C000-000000000046}" |
||||
# Component Methods |
||||
::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} UserSid 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} Context 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} |
||||
|
||||
# Dispatch Interface ComponentClient |
||||
set windowsinstaller::_dispatch_guids(ComponentClient) "{000C1098-0000-0000-C000-000000000046}" |
||||
# ComponentClient Methods |
||||
::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} ProductCode 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} UserSid 1033 2 {3 1033 2 25 {{{26 8} 10}} retval} |
||||
::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} Context 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} |
||||
|
@ -0,0 +1,745 @@
|
||||
# |
||||
# Copyright (c) 2006-2013 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Task scheduler API |
||||
|
||||
package require twapi_com |
||||
|
||||
namespace eval twapi { |
||||
variable CLSID_ITaskScheduler {{148BD52A-A2AB-11CE-B11F-00AA00530503}} |
||||
variable CLSID_ITask {{148BD520-A2AB-11CE-B11F-00AA00530503}} |
||||
} |
||||
|
||||
# Return an instance of the task scheduler |
||||
proc twapi::itaskscheduler_new {args} { |
||||
array set opts [parseargs args { |
||||
system.arg |
||||
} -maxleftover 0] |
||||
|
||||
# Get ITaskScheduler interface |
||||
set its [com_create_instance $::twapi::CLSID_ITaskScheduler -model inprocserver -interface ITaskScheduler -raw] |
||||
if {![info exists opts(system)]} { |
||||
return $its |
||||
} |
||||
trap { |
||||
itaskscheduler_set_target_system $its $opts(system) |
||||
} onerror {} { |
||||
IUnknown_Release $its |
||||
rethrow |
||||
} |
||||
return $its |
||||
} |
||||
|
||||
interp alias {} ::twapi::itaskscheduler_release {} ::twapi::IUnknown_Release |
||||
|
||||
# Return a new task interface |
||||
proc twapi::itaskscheduler_new_itask {its taskname} { |
||||
set iid_itask [name_to_iid ITask] |
||||
set iunk [ITaskScheduler_NewWorkItem $its $taskname $::twapi::CLSID_ITask $iid_itask] |
||||
trap { |
||||
set itask [Twapi_IUnknown_QueryInterface $iunk $iid_itask ITask] |
||||
} finally { |
||||
IUnknown_Release $iunk |
||||
} |
||||
return $itask |
||||
} |
||||
|
||||
# Get an existing task |
||||
proc twapi::itaskscheduler_get_itask {its taskname} { |
||||
set iid_itask [name_to_iid ITask] |
||||
set iunk [ITaskScheduler_Activate $its $taskname $iid_itask] |
||||
trap { |
||||
set itask [Twapi_IUnknown_QueryInterface $iunk $iid_itask ITask] |
||||
} finally { |
||||
IUnknown_Release $iunk |
||||
} |
||||
return $itask |
||||
} |
||||
|
||||
# Check if an itask exists |
||||
proc twapi::itaskscheduler_task_exists {its taskname} { |
||||
return [expr {[ITaskScheduler_IsOfType $its $taskname [name_to_iid ITask]] == 0 ? true : false}] |
||||
} |
||||
|
||||
# Return list of tasks |
||||
proc twapi::itaskscheduler_get_tasks {its} { |
||||
set ienum [ITaskScheduler_Enum $its] |
||||
trap { |
||||
set result [list ] |
||||
set more 1 |
||||
while {$more} { |
||||
lassign [IEnumWorkItems_Next $ienum 20] more items |
||||
set result [concat $result $items] |
||||
} |
||||
} finally { |
||||
IUnknown_Release $ienum |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# Sets the specified properties of the ITask |
||||
proc twapi::itask_configure {itask args} { |
||||
|
||||
array set opts [parseargs args { |
||||
application.arg |
||||
maxruntime.int |
||||
params.arg |
||||
priority.arg |
||||
workingdir.arg |
||||
account.arg |
||||
password.arg |
||||
comment.arg |
||||
creator.arg |
||||
data.arg |
||||
idlewait.int |
||||
idlewaitdeadline.int |
||||
interactive.bool |
||||
deletewhendone.bool |
||||
disabled.bool |
||||
hidden.bool |
||||
runonlyifloggedon.bool |
||||
startonlyifidle.bool |
||||
resumesystem.bool |
||||
killonidleend.bool |
||||
restartonidleresume.bool |
||||
dontstartonbatteries.bool |
||||
killifonbatteries.bool |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(priority)]} { |
||||
switch -exact -- $opts(priority) { |
||||
normal {set opts(priority) 0x00000020} |
||||
abovenormal {set opts(priority) 0x00008000} |
||||
belownormal {set opts(priority) 0x00004000} |
||||
high {set opts(priority) 0x00000080} |
||||
realtime {set opts(priority) 0x00000100} |
||||
idle {set opts(priority) 0x00000040} |
||||
default {error "Unknown priority '$opts(priority)'. Must be one of 'normal', 'high', 'idle' or 'realtime'"} |
||||
} |
||||
} |
||||
|
||||
foreach {opt fn} { |
||||
application ITask_SetApplicationName |
||||
maxruntime ITask_SetMaxRunTime |
||||
params ITask_SetParameters |
||||
workingdir ITask_SetWorkingDirectory |
||||
priority ITask_SetPriority |
||||
comment IScheduledWorkItem_SetComment |
||||
creator IScheduledWorkItem_SetCreator |
||||
data IScheduledWorkItem_SetWorkItemData |
||||
errorretrycount IScheduledWorkItem_SetErrorRetryCount |
||||
errorretryinterval IScheduledWorkItem_SetErrorRetryInterval |
||||
} { |
||||
if {[info exists opts($opt)]} { |
||||
$fn $itask $opts($opt) |
||||
} |
||||
} |
||||
|
||||
if {[info exists opts(account)]} { |
||||
if {$opts(account) ne ""} { |
||||
if {![info exists opts(password)]} { |
||||
error "Option -password must be specified if -account is specified" |
||||
} |
||||
} else { |
||||
# System account. Set password to NULL pointer indicated |
||||
# by magic null pointer |
||||
set opts(password) $::twapi::nullptr |
||||
} |
||||
IScheduledWorkItem_SetAccountInformation $itask $opts(account) $opts(password) |
||||
} |
||||
|
||||
if {[info exists opts(idlewait)] || [info exists opts(idlewaitdeadline)]} { |
||||
# If either one is not specified, get the current settings |
||||
if {! ([info exists opts(idlewait)] && |
||||
[info exists opts(idlewaitdeadline)]) } { |
||||
lassign [IScheduledWorkItem_GetIdleWait $itask] idle dead |
||||
if {![info exists opts(idlewait)]} { |
||||
set opts(idlewait) $idle |
||||
} |
||||
if {![info exists opts(idlewaitdeadline)]} { |
||||
set opts(idlewaitdeadline) $dead |
||||
} |
||||
} |
||||
IScheduledWorkItem_SetIdleWait $itask $opts(idlewait) $opts(idlewaitdeadline) |
||||
} |
||||
|
||||
# Finally figure out and set the flags if needed |
||||
if {[info exists opts(interactive)] || |
||||
[info exists opts(deletewhendone)] || |
||||
[info exists opts(disabled)] || |
||||
[info exists opts(hidden)] || |
||||
[info exists opts(runonlyifloggedon)] || |
||||
[info exists opts(startonlyifidle)] || |
||||
[info exists opts(resumesystem)] || |
||||
[info exists opts(killonidleend)] || |
||||
[info exists opts(restartonidleresume)] || |
||||
[info exists opts(dontstartonbatteries)] || |
||||
[info exists opts(killifonbatteries)]} { |
||||
|
||||
# First, get the current flags |
||||
set flags [IScheduledWorkItem_GetFlags $itask] |
||||
foreach {opt val} { |
||||
interactive 0x1 |
||||
deletewhendone 0x2 |
||||
disabled 0x4 |
||||
startonlyifidle 0x10 |
||||
hidden 0x200 |
||||
runonlyifloggedon 0x2000 |
||||
resumesystem 0x1000 |
||||
killonidleend 0x20 |
||||
restartonidleresume 0x800 |
||||
dontstartonbatteries 0x40 |
||||
killifonbatteries 0x80 |
||||
} { |
||||
# Set / reset the bit if specified |
||||
if {[info exists opts($opt)]} { |
||||
if {$opts($opt)} { |
||||
setbits flags $val |
||||
} else { |
||||
resetbits flags $val |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Now set the new value of flags |
||||
IScheduledWorkItem_SetFlags $itask $flags |
||||
} |
||||
|
||||
|
||||
return |
||||
} |
||||
|
||||
proc twapi::itask_get_info {itask args} { |
||||
# Note options errorretrycount and errorretryinterval are not implemented |
||||
# by the OS so left out |
||||
array set opts [parseargs args { |
||||
all |
||||
application |
||||
maxruntime |
||||
params |
||||
priority |
||||
workingdir |
||||
account |
||||
comment |
||||
creator |
||||
data |
||||
idlewait |
||||
idlewaitdeadline |
||||
interactive |
||||
deletewhendone |
||||
disabled |
||||
hidden |
||||
runonlyifloggedon |
||||
startonlyifidle |
||||
resumesystem |
||||
killonidleend |
||||
restartonidleresume |
||||
dontstartonbatteries |
||||
killifonbatteries |
||||
lastruntime |
||||
nextruntime |
||||
status |
||||
} -maxleftover 0] |
||||
|
||||
set result [list ] |
||||
if {$opts(all) || $opts(priority)} { |
||||
switch -exact -- [twapi::ITask_GetPriority $itask] { |
||||
32 { set priority normal } |
||||
64 { set priority idle } |
||||
128 { set priority high } |
||||
256 { set priority realtime } |
||||
16384 { set priority belownormal } |
||||
32768 { set priority abovenormal } |
||||
default { set priority unknown } |
||||
} |
||||
lappend result -priority $priority |
||||
} |
||||
|
||||
foreach {opt fn} { |
||||
application ITask_GetApplicationName |
||||
maxruntime ITask_GetMaxRunTime |
||||
params ITask_GetParameters |
||||
workingdir ITask_GetWorkingDirectory |
||||
account IScheduledWorkItem_GetAccountInformation |
||||
comment IScheduledWorkItem_GetComment |
||||
creator IScheduledWorkItem_GetCreator |
||||
data IScheduledWorkItem_GetWorkItemData |
||||
} { |
||||
if {$opts(all) || $opts($opt)} { |
||||
trap { |
||||
lappend result -$opt [$fn $itask] |
||||
} onerror {TWAPI_WIN32 -2147216625} { |
||||
# THe information is empty in the scheduler database |
||||
lappend result -$opt {} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if {$opts(all) || $opts(lastruntime)} { |
||||
trap { |
||||
lappend result -lastruntime [_timelist_to_timestring [IScheduledWorkItem_GetMostRecentRunTime $itask]] |
||||
} onerror {TWAPI_WIN32 267011} { |
||||
# Not run yet at all |
||||
lappend result -lastruntime {} |
||||
} |
||||
} |
||||
|
||||
if {$opts(all) || $opts(nextruntime)} { |
||||
trap { |
||||
lappend result -nextruntime [_timelist_to_timestring [IScheduledWorkItem_GetNextRunTime $itask]] |
||||
} onerror {TWAPI_WIN32 267010} { |
||||
# Task is disabled |
||||
lappend result -nextruntime disabled |
||||
} onerror {TWAPI_WIN32 267015} { |
||||
# No triggers set |
||||
lappend result -nextruntime notriggers |
||||
} onerror {TWAPI_WIN32 267016} { |
||||
# No triggers set |
||||
lappend result -nextruntime oneventonly |
||||
} |
||||
} |
||||
|
||||
if {$opts(all) || $opts(status)} { |
||||
set status [IScheduledWorkItem_GetStatus $itask] |
||||
if {$status == 0x41300} { |
||||
set status ready |
||||
} elseif {$status == 0x41301} { |
||||
set status running |
||||
} elseif {$status == 0x41302} { |
||||
set status disabled |
||||
} elseif {$status == 0x41305} { |
||||
set status partiallydefined |
||||
} else { |
||||
set status unknown |
||||
} |
||||
lappend result -status $status |
||||
} |
||||
|
||||
|
||||
if {$opts(all) || $opts(idlewait) || $opts(idlewaitdeadline)} { |
||||
lassign [IScheduledWorkItem_GetIdleWait $itask] idle dead |
||||
if {$opts(all) || $opts(idlewait)} { |
||||
lappend result -idlewait $idle |
||||
} |
||||
if {$opts(all) || $opts(idlewaitdeadline)} { |
||||
lappend result -idlewaitdeadline $dead |
||||
} |
||||
} |
||||
|
||||
# Finally figure out and set the flags if needed |
||||
if {$opts(all) || |
||||
$opts(interactive) || |
||||
$opts(deletewhendone) || |
||||
$opts(disabled) || |
||||
$opts(hidden) || |
||||
$opts(runonlyifloggedon) || |
||||
$opts(startonlyifidle) || |
||||
$opts(resumesystem) || |
||||
$opts(killonidleend) || |
||||
$opts(restartonidleresume) || |
||||
$opts(dontstartonbatteries) || |
||||
$opts(killifonbatteries)} { |
||||
|
||||
# First, get the current flags |
||||
set flags [IScheduledWorkItem_GetFlags $itask] |
||||
foreach {opt val} { |
||||
interactive 0x1 |
||||
deletewhendone 0x2 |
||||
disabled 0x4 |
||||
startonlyifidle 0x10 |
||||
hidden 0x200 |
||||
runonlyifloggedon 0x2000 |
||||
resumesystem 0x1000 |
||||
killonidleend 0x20 |
||||
restartonidleresume 0x800 |
||||
dontstartonbatteries 0x40 |
||||
killifonbatteries 0x80 |
||||
} { |
||||
if {$opts(all) || $opts($opt)} { |
||||
lappend result -$opt [expr {($flags & $val) ? true : false}] |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
return $result |
||||
} |
||||
|
||||
# Get the runtimes for a task within an interval |
||||
proc twapi::itask_get_runtimes_within_interval {itask args} { |
||||
array set opts [parseargs args { |
||||
start.arg |
||||
end.arg |
||||
{count.int 1} |
||||
statusvar.arg |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(start)]} { |
||||
set start [_timestring_to_timelist $opts(start)] |
||||
} else { |
||||
set start [_seconds_to_timelist [clock seconds]] |
||||
} |
||||
if {[info exists opts(end)]} { |
||||
set end [_timestring_to_timelist $opts(end)] |
||||
} else { |
||||
set end {2038 1 1 0 0 0 0} |
||||
} |
||||
|
||||
set result [list ] |
||||
if {[info exists opts(statusvar)]} { |
||||
upvar $opts(statusvar) status |
||||
} |
||||
lassign [IScheduledWorkItem_GetRunTimes $itask $start $end $opts(count)] status timelist |
||||
|
||||
foreach time $timelist { |
||||
lappend result [_timelist_to_timestring $time] |
||||
} |
||||
|
||||
|
||||
return $result |
||||
} |
||||
|
||||
# Saves the specified ITask |
||||
proc twapi::itask_save {itask} { |
||||
set ipersist [Twapi_IUnknown_QueryInterface $itask [name_to_iid IPersistFile] IPersistFile] |
||||
trap { |
||||
IPersistFile_Save $ipersist "" 1 |
||||
} finally { |
||||
IUnknown_Release $ipersist |
||||
} |
||||
return |
||||
} |
||||
|
||||
# Show property editor for a task |
||||
proc twapi::itask_edit_dialog {itask args} { |
||||
array set opts [parseargs args { |
||||
{hwin.arg 0} |
||||
} -maxleftover 0] |
||||
|
||||
return [twapi::IScheduledWorkItem_EditWorkItem $itask $opts(hwin) 0] |
||||
} |
||||
|
||||
|
||||
interp alias {} ::twapi::itask_release {} ::twapi::IUnknown_Release |
||||
|
||||
# Get information about a trigger |
||||
proc twapi::itasktrigger_get_info {itt} { |
||||
array set data [ITaskTrigger_GetTrigger $itt] |
||||
|
||||
set result(-begindate) [format %04d-%02d-%02d $data(wBeginYear) $data(wBeginMonth) $data(wBeginDay)] |
||||
|
||||
set result(-starttime) [format %02d:%02d $data(wStartHour) $data(wStartMinute)] |
||||
|
||||
if {$data(rgFlags) & 1} { |
||||
set result(-enddate) [format %04d-%02d-%02d $data(wEndYear) $data(wEndMonth) $data(wEndDay)] |
||||
} else { |
||||
set result(-enddate) "" |
||||
} |
||||
|
||||
set result(-duration) $data(MinutesDuration) |
||||
set result(-interval) $data(MinutesInterval) |
||||
if {$data(rgFlags) & 2} { |
||||
set result(-killatdurationend) true |
||||
} else { |
||||
set result(-killatdurationend) false |
||||
} |
||||
|
||||
if {$data(rgFlags) & 4} { |
||||
set result(-disabled) true |
||||
} else { |
||||
set result(-disabled) false |
||||
} |
||||
|
||||
switch -exact -- [lindex $data(type) 0] { |
||||
0 { |
||||
set result(-type) once |
||||
} |
||||
1 { |
||||
set result(-type) daily |
||||
set result(-period) [lindex $data(type) 1] |
||||
} |
||||
2 { |
||||
set result(-type) weekly |
||||
set result(-period) [lindex $data(type) 1] |
||||
set result(-weekdays) [format 0x%x [lindex $data(type) 2]] |
||||
} |
||||
3 { |
||||
set result(-type) monthlydate |
||||
set result(-daysofmonth) [format 0x%x [lindex $data(type) 1]] |
||||
set result(-months) [format 0x%x [lindex $data(type) 2]] |
||||
} |
||||
4 { |
||||
set result(-type) monthlydow |
||||
set result(-weekofmonth) [lindex {first second third fourth last} [lindex $data(type) 2]] |
||||
set result(-weekdays) [format 0x%x [lindex $data(type) 2]] |
||||
set result(-months) [format 0x%x [lindex $data(type) 3]] |
||||
} |
||||
5 { |
||||
set result(-type) onidle |
||||
} |
||||
6 { |
||||
set result(-type) atsystemstart |
||||
} |
||||
7 { |
||||
set result(-type) atlogon |
||||
} |
||||
} |
||||
return [array get result] |
||||
} |
||||
|
||||
|
||||
# Configure a task trigger |
||||
proc twapi::itasktrigger_configure {itt args} { |
||||
array set opts [parseargs args { |
||||
begindate.arg |
||||
enddate.arg |
||||
starttime.arg |
||||
interval.int |
||||
duration.int |
||||
killatdurationend.bool |
||||
disabled.bool |
||||
type.arg |
||||
weekofmonth.int |
||||
{period.int 1} |
||||
{weekdays.int 0x7f} |
||||
{daysofmonth.int 0x7fffffff} |
||||
{months.int 0xfff} |
||||
} -maxleftover 0] |
||||
|
||||
|
||||
array set data [ITaskTrigger_GetTrigger $itt] |
||||
|
||||
if {[info exists opts(begindate)]} { |
||||
lassign [split $opts(begindate) -] year month day |
||||
# Note we trim leading zeroes else Tcl thinks its octal |
||||
set data(wBeginYear) [scan $year %d] |
||||
set data(wBeginMonth) [scan $month %d] |
||||
set data(wBeginDay) [scan $day %d] |
||||
} |
||||
|
||||
if {[info exists opts(starttime)]} { |
||||
lassign [split $opts(starttime) :] hour minute |
||||
# Note we trim leading zeroes else Tcl thinks its octal |
||||
set data(wStartHour) [scan $hour %d] |
||||
set data(wStartMinute) [scan $minute %d] |
||||
} |
||||
|
||||
if {[info exists opts(enddate)]} { |
||||
if {$opts(enddate) ne ""} { |
||||
setbits data(rgFlags) 1; # Indicate end date is present |
||||
lassign [split $opts(enddate) -] year month day |
||||
# Note we trim leading zeroes else Tcl thinks its octal |
||||
set data(wEndYear) [scan $year %d] |
||||
set data(wEndMonth) [scan $month %d] |
||||
set data(wEndDay) [scan $day %d] |
||||
} else { |
||||
resetbits data(rgFlags) 1; # Indicate no end date |
||||
} |
||||
} |
||||
|
||||
|
||||
if {[info exists opts(duration)]} { |
||||
set data(MinutesDuration) $opts(duration) |
||||
} |
||||
|
||||
if {[info exists opts(interval)]} { |
||||
set data(MinutesInterval) $opts(interval) |
||||
} |
||||
|
||||
if {[info exists opts(killatdurationend)]} { |
||||
if {$opts(killatdurationend)} { |
||||
setbits data(rgFlags) 2 |
||||
} else { |
||||
resetbits data(rgFlags) 2 |
||||
} |
||||
} |
||||
|
||||
if {[info exists opts(disabled)]} { |
||||
if {$opts(disabled)} { |
||||
setbits data(rgFlags) 4 |
||||
} else { |
||||
resetbits data(rgFlags) 4 |
||||
} |
||||
} |
||||
|
||||
# Note the type specific options are only used if -type is specified |
||||
if {[info exists opts(type)]} { |
||||
switch -exact -- $opts(type) { |
||||
once { |
||||
set data(type) [list 0] |
||||
} |
||||
daily { |
||||
set data(type) [list 1 $opts(period)] |
||||
} |
||||
weekly { |
||||
set data(type) [list 2 $opts(period) $opts(weekdays)] |
||||
} |
||||
monthlydate { |
||||
set data(type) [list 3 $opts(daysofmonth) $opts(months)] |
||||
} |
||||
monthlydow { |
||||
set data(type) [list 4 $opts(weekofmonth) $opts(weekdays) $opts(months)] |
||||
} |
||||
onidle { |
||||
set data(type) [list 5] |
||||
} |
||||
atsystemstart { |
||||
set data(type) [list 6] |
||||
} |
||||
atlogon { |
||||
set data(type) [list 7] |
||||
} |
||||
} |
||||
} |
||||
|
||||
ITaskTrigger_SetTrigger $itt [array get data] |
||||
return |
||||
} |
||||
|
||||
interp alias {} ::twapi::itasktrigger_release {} ::twapi::IUnknown_Release |
||||
|
||||
# Create a new task from scratch. Basically a wrapper around the |
||||
# corresponding itaskscheduler, itask and itasktrigger calls |
||||
proc twapi::mstask_create {taskname args} { |
||||
|
||||
# The options are a combination of itask_configure and |
||||
# itasktrigger_configure. |
||||
# Note the disabled option default to false explicitly. This is because |
||||
# the task trigger will default to disabled unless specifically set. |
||||
array set opts [parseargs args { |
||||
system.arg |
||||
application.arg |
||||
maxruntime.int |
||||
params.arg |
||||
priority.arg |
||||
workingdir.arg |
||||
account.arg |
||||
password.arg |
||||
comment.arg |
||||
creator.arg |
||||
data.arg |
||||
idlewait.int |
||||
idlewaitdeadline.int |
||||
interactive.bool |
||||
deletewhendone.bool |
||||
{disabled.bool false} |
||||
hidden.bool |
||||
runonlyifloggedon.bool |
||||
startonlyifidle.bool |
||||
resumesystem.bool |
||||
killonidleend.bool |
||||
restartonidleresume.bool |
||||
dontstartonbatteries.bool |
||||
killifonbatteries.bool |
||||
begindate.arg |
||||
enddate.arg |
||||
starttime.arg |
||||
interval.int |
||||
duration.int |
||||
killatdurationend.bool |
||||
type.arg |
||||
period.int |
||||
weekdays.int |
||||
daysofmonth.int |
||||
months.int |
||||
} -maxleftover 0] |
||||
|
||||
set its [itaskscheduler_new] |
||||
trap { |
||||
if {[info exists opts(system)]} { |
||||
itaskscheduler_set_target_system $opts(system) |
||||
} |
||||
|
||||
set itask [itaskscheduler_new_itask $its $taskname] |
||||
# Construct the command line for configuring the task |
||||
set cmd [list itask_configure $itask] |
||||
foreach opt { |
||||
application |
||||
maxruntime |
||||
params |
||||
priority |
||||
workingdir |
||||
account |
||||
password |
||||
comment |
||||
creator |
||||
data |
||||
idlewait |
||||
idlewaitdeadline |
||||
interactive |
||||
deletewhendone |
||||
disabled |
||||
hidden |
||||
runonlyifloggedon |
||||
startonlyifidle |
||||
resumesystem |
||||
killonidleend |
||||
restartonidleresume |
||||
dontstartonbatteries |
||||
killifonbatteries |
||||
} { |
||||
if {[info exists opts($opt)]} { |
||||
lappend cmd -$opt $opts($opt) |
||||
} |
||||
} |
||||
eval $cmd |
||||
|
||||
# Now get a trigger and configure it |
||||
set itt [lindex [itask_new_itasktrigger $itask] 1] |
||||
set cmd [list itasktrigger_configure $itt] |
||||
foreach opt { |
||||
begindate |
||||
enddate |
||||
interval |
||||
starttime |
||||
duration |
||||
killatdurationend |
||||
type |
||||
period |
||||
weekdays |
||||
daysofmonth |
||||
months |
||||
disabled |
||||
} { |
||||
if {[info exists opts($opt)]} { |
||||
lappend cmd -$opt $opts($opt) |
||||
} |
||||
} |
||||
eval $cmd |
||||
|
||||
# Save the task |
||||
itask_save $itask |
||||
|
||||
} finally { |
||||
IUnknown_Release $its |
||||
if {[info exists itask]} { |
||||
IUnknown_Release $itask |
||||
} |
||||
if {[info exists itt]} { |
||||
IUnknown_Release $itt |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
# Delete a task |
||||
proc twapi::mstask_delete {taskname args} { |
||||
# The options are a combination of itask_configure and |
||||
# itasktrigger_configure |
||||
array set opts [parseargs args { |
||||
system.arg |
||||
} -maxleftover 0] |
||||
set its [itaskscheduler_new] |
||||
trap { |
||||
if {[info exists opts(system)]} { |
||||
itaskscheduler_set_target_system $opts(system) |
||||
} |
||||
itaskscheduler_delete_task $its $taskname |
||||
} finally { |
||||
IUnknown_Release $its |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,75 @@
|
||||
# |
||||
# Copyright (c) 2012 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Generate sound for the specified duration |
||||
proc twapi::beep {args} { |
||||
array set opts [parseargs args { |
||||
{frequency.int 1000} |
||||
{duration.int 100} |
||||
{type.arg} |
||||
}] |
||||
|
||||
if {[info exists opts(type)]} { |
||||
switch -exact -- $opts(type) { |
||||
ok {MessageBeep 0} |
||||
hand {MessageBeep 0x10} |
||||
question {MessageBeep 0x20} |
||||
exclaimation {MessageBeep 0x30} |
||||
exclamation {MessageBeep 0x30} |
||||
asterisk {MessageBeep 0x40} |
||||
default {error "Unknown sound type '$opts(type)'"} |
||||
} |
||||
return |
||||
} |
||||
Beep $opts(frequency) $opts(duration) |
||||
return |
||||
} |
||||
|
||||
# Play the specified sound |
||||
proc twapi::play_sound {name args} { |
||||
array set opts [parseargs args { |
||||
alias |
||||
async |
||||
loop |
||||
nodefault |
||||
wait |
||||
nostop |
||||
}] |
||||
|
||||
if {$opts(alias)} { |
||||
set flags 0x00010000; #SND_ALIAS |
||||
} else { |
||||
set flags 0x00020000; #SND_FILENAME |
||||
} |
||||
if {$opts(loop)} { |
||||
# Note LOOP requires ASYNC |
||||
setbits flags 0x9; #SND_LOOP | SND_ASYNC |
||||
} else { |
||||
if {$opts(async)} { |
||||
setbits flags 0x0001; #SND_ASYNC |
||||
} else { |
||||
setbits flags 0x0000; #SND_SYNC |
||||
} |
||||
} |
||||
|
||||
if {$opts(nodefault)} { |
||||
setbits flags 0x0002; #SND_NODEFAULT |
||||
} |
||||
|
||||
if {! $opts(wait)} { |
||||
setbits flags 0x00002000; #SND_NOWAIT |
||||
} |
||||
|
||||
if {$opts(nostop)} { |
||||
setbits flags 0x0010; #SND_NOSTOP |
||||
} |
||||
|
||||
return [PlaySound $name 0 $flags] |
||||
} |
||||
|
||||
proc twapi::stop_sound {} { |
||||
PlaySound "" 0 0x0040; #SND_PURGE |
||||
} |
@ -0,0 +1,103 @@
|
||||
# |
||||
# Copyright (c) 2010-2011, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Implementation of named pipes |
||||
|
||||
proc twapi::namedpipe_server {name args} { |
||||
set name [file nativename $name] |
||||
|
||||
# Only byte mode currently supported. Message mode does |
||||
# not mesh well with Tcl channel infrastructure. |
||||
# readmode.arg |
||||
# writemode.arg |
||||
|
||||
array set opts [twapi::parseargs args { |
||||
{access.arg {read write}} |
||||
{writedacl 0 0x00040000} |
||||
{writeowner 0 0x00080000} |
||||
{writesacl 0 0x01000000} |
||||
{writethrough 0 0x80000000} |
||||
denyremote |
||||
{timeout.int 50} |
||||
{maxinstances.int 255} |
||||
{secd.arg {}} |
||||
{inherit.bool 0} |
||||
} -maxleftover 0] |
||||
|
||||
# 0x40000000 -> OVERLAPPED I/O |
||||
set open_mode [expr { |
||||
[twapi::_parse_symbolic_bitmask $opts(access) {read 1 write 2}] | |
||||
$opts(writedacl) | $opts(writeowner) | |
||||
$opts(writesacl) | $opts(writethrough) | |
||||
0x40000000 |
||||
}] |
||||
|
||||
set pipe_mode 0 |
||||
if {$opts(denyremote)} { |
||||
if {! [twapi::min_os_version 6]} { |
||||
error "Option -denyremote not supported on this operating system." |
||||
} |
||||
set pipe_mode [expr {$pipe_mode | 8}] |
||||
} |
||||
|
||||
return [twapi::Twapi_NPipeServer $name $open_mode $pipe_mode \ |
||||
$opts(maxinstances) 4000 4000 $opts(timeout) \ |
||||
[_make_secattr $opts(secd) $opts(inherit)]] |
||||
} |
||||
|
||||
proc twapi::namedpipe_client {name args} { |
||||
set name [file nativename $name] |
||||
|
||||
# Only byte mode currently supported. Message mode does |
||||
# not mesh well with Tcl channel infrastructure. |
||||
# readmode.arg |
||||
# writemode.arg |
||||
|
||||
array set opts [twapi::parseargs args { |
||||
{access.arg {read write}} |
||||
impersonationlevel.arg |
||||
{impersonateeffectiveonly.bool false 0x00080000} |
||||
{impersonatecontexttracking.bool false 0x00040000} |
||||
} -maxleftover 0] |
||||
|
||||
# FILE_READ_DATA 0x00000001 |
||||
# FILE_WRITE_DATA 0x00000002 |
||||
# Note - use _parse_symbolic_bitmask because we allow user to specify |
||||
# numeric masks as well |
||||
set desired_access [twapi::_parse_symbolic_bitmask $opts(access) { |
||||
read 1 |
||||
write 2 |
||||
}] |
||||
|
||||
set flags 0 |
||||
if {[info exists opts(impersonationlevel)]} { |
||||
switch -exact -- $opts(impersonationlevel) { |
||||
anonymous { set flags 0x00100000 } |
||||
identification { set flags 0x00110000 } |
||||
impersonation { set flags 0x00120000 } |
||||
delegation { set flags 0x00130000 } |
||||
default { |
||||
# ERROR_BAD_IMPERSONATION_LEVEL |
||||
win32_error 1346 "Invalid impersonation level '$opts(impersonationlevel)'." |
||||
} |
||||
} |
||||
set flags [expr {$flags | $opts(impersonateeffectiveonly) | |
||||
$opts(impersonatecontexttracking)}] |
||||
} |
||||
|
||||
set share_mode 0; # Share none |
||||
set secattr {}; # At some point use this for "inherit" flag |
||||
set create_disposition 3; # OPEN_EXISTING |
||||
return [twapi::Twapi_NPipeClient $name $desired_access $share_mode \ |
||||
$secattr $create_disposition $flags] |
||||
} |
||||
|
||||
# Impersonate a named pipe client |
||||
proc twapi::impersonate_namedpipe_client {chan} { |
||||
set h [get_tcl_channel_handle $chan read] |
||||
ImpersonateNamedPipeClient $h |
||||
} |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,467 @@
|
||||
# |
||||
# Copyright (c) 2003-2013, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi {} |
||||
|
||||
# Compatibility alias |
||||
interp alias {} twapi::get_user_default_langid {} twapi::get_user_langid |
||||
interp alias {} twapi::get_system_default_langid {} twapi::get_system_langid |
||||
|
||||
# |
||||
# Format a number |
||||
proc twapi::format_number {number lcid args} { |
||||
|
||||
set number [_verify_number_format $number] |
||||
|
||||
set lcid [_map_default_lcid_token $lcid] |
||||
|
||||
# If no options specified, format according to the passed locale |
||||
if {[llength $args] == 0} { |
||||
return [GetNumberFormat 1 $lcid 0 $number 0 0 0 . "" 0] |
||||
} |
||||
|
||||
array set opts [parseargs args { |
||||
idigits.int |
||||
ilzero.bool |
||||
sgrouping.int |
||||
sdecimal.arg |
||||
sthousand.arg |
||||
inegnumber.int |
||||
}] |
||||
|
||||
# Check the locale for unspecified options |
||||
foreach opt {idigits ilzero sgrouping sdecimal sthousand inegnumber} { |
||||
if {![info exists opts($opt)]} { |
||||
set opts($opt) [lindex [get_locale_info $lcid -$opt] 1] |
||||
} |
||||
} |
||||
|
||||
# If number of decimals is -1, see how many decimal places |
||||
# in passed string |
||||
if {$opts(idigits) == -1} { |
||||
lassign [split $number .] whole frac |
||||
set opts(idigits) [string length $frac] |
||||
} |
||||
|
||||
# Convert Locale format for grouping to integer calue |
||||
if {![string is integer $opts(sgrouping)]} { |
||||
# Format assumed to be of the form "N;M;....;0" |
||||
set grouping 0 |
||||
foreach n [split $opts(sgrouping) {;}] { |
||||
if {$n == 0} break |
||||
set grouping [expr {$n + 10*$grouping}] |
||||
} |
||||
set opts(sgrouping) $grouping |
||||
} |
||||
|
||||
set flags 0 |
||||
if {[info exists opts(nouseroverride)] && $opts(nouseroverride)} { |
||||
setbits flags 0x80000000 |
||||
} |
||||
return [GetNumberFormat 0 $lcid $flags $number $opts(idigits) \ |
||||
$opts(ilzero) $opts(sgrouping) $opts(sdecimal) \ |
||||
$opts(sthousand) $opts(inegnumber)] |
||||
} |
||||
|
||||
|
||||
# |
||||
# Format currency |
||||
proc twapi::format_currency {number lcid args} { |
||||
|
||||
set number [_verify_number_format $number] |
||||
|
||||
# Get semi-canonical form (get rid of preceding "+" etc.) |
||||
# Also verifies number syntax |
||||
set number [expr {$number+0}]; |
||||
|
||||
set lcid [_map_default_lcid_token $lcid] |
||||
|
||||
# If no options specified, format according to the passed locale |
||||
if {[llength $args] == 0} { |
||||
return [GetCurrencyFormat 1 $lcid 0 $number 0 0 0 . "" 0 0 ""] |
||||
} |
||||
|
||||
array set opts [parseargs args { |
||||
idigits.int |
||||
ilzero.bool |
||||
sgrouping.int |
||||
sdecimal.arg |
||||
sthousand.arg |
||||
inegcurr.int |
||||
icurrency.int |
||||
scurrency.arg |
||||
}] |
||||
|
||||
# Check the locale for unspecified options |
||||
foreach opt {idigits ilzero sgrouping sdecimal sthousand inegcurr icurrency scurrency} { |
||||
if {![info exists opts($opt)]} { |
||||
set opts($opt) [lindex [get_locale_info $lcid -$opt] 1] |
||||
} |
||||
} |
||||
|
||||
# If number of decimals is -1, see how many decimal places |
||||
# in passed string |
||||
if {$opts(idigits) == -1} { |
||||
lassign [split $number .] whole frac |
||||
set opts(idigits) [string length $frac] |
||||
} |
||||
|
||||
# Convert Locale format for grouping to integer calue |
||||
if {![string is integer $opts(sgrouping)]} { |
||||
# Format assumed to be of the form "N;M;....;0" |
||||
set grouping 0 |
||||
foreach n [split $opts(sgrouping) {;}] { |
||||
if {$n == 0} break |
||||
set grouping [expr {$n + 10*$grouping}] |
||||
} |
||||
set opts(sgrouping) $grouping |
||||
} |
||||
|
||||
set flags 0 |
||||
if {[info exists opts(nouseroverride)] && $opts(nouseroverride)} { |
||||
setbits flags 0x80000000 |
||||
} |
||||
|
||||
return [GetCurrencyFormat 0 $lcid $flags $number $opts(idigits) \ |
||||
$opts(ilzero) $opts(sgrouping) $opts(sdecimal) \ |
||||
$opts(sthousand) $opts(inegcurr) \ |
||||
$opts(icurrency) $opts(scurrency)] |
||||
} |
||||
|
||||
|
||||
# |
||||
# Get various info about a locale |
||||
proc twapi::get_locale_info {lcid args} { |
||||
|
||||
set lcid [_map_default_lcid_token $lcid] |
||||
|
||||
variable locale_info_class_map |
||||
if {![info exists locale_info_class_map]} { |
||||
# TBD - ilanguage not recommended for Vista. Remove it? |
||||
array set locale_info_class_map { |
||||
ilanguage 0x00000001 |
||||
slanguage 0x00000002 |
||||
senglanguage 0x00001001 |
||||
sabbrevlangname 0x00000003 |
||||
snativelangname 0x00000004 |
||||
icountry 0x00000005 |
||||
scountry 0x00000006 |
||||
sengcountry 0x00001002 |
||||
sabbrevctryname 0x00000007 |
||||
snativectryname 0x00000008 |
||||
idefaultlanguage 0x00000009 |
||||
idefaultcountry 0x0000000A |
||||
idefaultcodepage 0x0000000B |
||||
idefaultansicodepage 0x00001004 |
||||
idefaultmaccodepage 0x00001011 |
||||
slist 0x0000000C |
||||
imeasure 0x0000000D |
||||
sdecimal 0x0000000E |
||||
sthousand 0x0000000F |
||||
sgrouping 0x00000010 |
||||
idigits 0x00000011 |
||||
ilzero 0x00000012 |
||||
inegnumber 0x00001010 |
||||
snativedigits 0x00000013 |
||||
scurrency 0x00000014 |
||||
sintlsymbol 0x00000015 |
||||
smondecimalsep 0x00000016 |
||||
smonthousandsep 0x00000017 |
||||
smongrouping 0x00000018 |
||||
icurrdigits 0x00000019 |
||||
iintlcurrdigits 0x0000001A |
||||
icurrency 0x0000001B |
||||
inegcurr 0x0000001C |
||||
sdate 0x0000001D |
||||
stime 0x0000001E |
||||
sshortdate 0x0000001F |
||||
slongdate 0x00000020 |
||||
stimeformat 0x00001003 |
||||
idate 0x00000021 |
||||
ildate 0x00000022 |
||||
itime 0x00000023 |
||||
itimemarkposn 0x00001005 |
||||
icentury 0x00000024 |
||||
itlzero 0x00000025 |
||||
idaylzero 0x00000026 |
||||
imonlzero 0x00000027 |
||||
s1159 0x00000028 |
||||
s2359 0x00000029 |
||||
icalendartype 0x00001009 |
||||
ioptionalcalendar 0x0000100B |
||||
ifirstdayofweek 0x0000100C |
||||
ifirstweekofyear 0x0000100D |
||||
sdayname1 0x0000002A |
||||
sdayname2 0x0000002B |
||||
sdayname3 0x0000002C |
||||
sdayname4 0x0000002D |
||||
sdayname5 0x0000002E |
||||
sdayname6 0x0000002F |
||||
sdayname7 0x00000030 |
||||
sabbrevdayname1 0x00000031 |
||||
sabbrevdayname2 0x00000032 |
||||
sabbrevdayname3 0x00000033 |
||||
sabbrevdayname4 0x00000034 |
||||
sabbrevdayname5 0x00000035 |
||||
sabbrevdayname6 0x00000036 |
||||
sabbrevdayname7 0x00000037 |
||||
smonthname1 0x00000038 |
||||
smonthname2 0x00000039 |
||||
smonthname3 0x0000003A |
||||
smonthname4 0x0000003B |
||||
smonthname5 0x0000003C |
||||
smonthname6 0x0000003D |
||||
smonthname7 0x0000003E |
||||
smonthname8 0x0000003F |
||||
smonthname9 0x00000040 |
||||
smonthname10 0x00000041 |
||||
smonthname11 0x00000042 |
||||
smonthname12 0x00000043 |
||||
smonthname13 0x0000100E |
||||
sabbrevmonthname1 0x00000044 |
||||
sabbrevmonthname2 0x00000045 |
||||
sabbrevmonthname3 0x00000046 |
||||
sabbrevmonthname4 0x00000047 |
||||
sabbrevmonthname5 0x00000048 |
||||
sabbrevmonthname6 0x00000049 |
||||
sabbrevmonthname7 0x0000004A |
||||
sabbrevmonthname8 0x0000004B |
||||
sabbrevmonthname9 0x0000004C |
||||
sabbrevmonthname10 0x0000004D |
||||
sabbrevmonthname11 0x0000004E |
||||
sabbrevmonthname12 0x0000004F |
||||
sabbrevmonthname13 0x0000100F |
||||
spositivesign 0x00000050 |
||||
snegativesign 0x00000051 |
||||
ipossignposn 0x00000052 |
||||
inegsignposn 0x00000053 |
||||
ipossymprecedes 0x00000054 |
||||
ipossepbyspace 0x00000055 |
||||
inegsymprecedes 0x00000056 |
||||
inegsepbyspace 0x00000057 |
||||
fontsignature 0x00000058 |
||||
siso639langname 0x00000059 |
||||
siso3166ctryname 0x0000005A |
||||
idefaultebcdiccodepage 0x00001012 |
||||
ipapersize 0x0000100A |
||||
sengcurrname 0x00001007 |
||||
snativecurrname 0x00001008 |
||||
syearmonth 0x00001006 |
||||
ssortname 0x00001013 |
||||
idigitsubstitution 0x00001014 |
||||
} |
||||
} |
||||
|
||||
# array set opts [parseargs args [array names locale_info_class_map]] |
||||
|
||||
set result [list ] |
||||
foreach opt $args { |
||||
lappend result $opt [GetLocaleInfo $lcid $locale_info_class_map([string range $opt 1 end])] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
|
||||
proc twapi::map_code_page_to_name {cp} { |
||||
set code_page_names { |
||||
0 "System ANSI default" |
||||
1 "System OEM default" |
||||
37 "IBM EBCDIC - U.S./Canada" |
||||
437 "OEM - United States" |
||||
500 "IBM EBCDIC - International" |
||||
708 "Arabic - ASMO 708" |
||||
709 "Arabic - ASMO 449+, BCON V4" |
||||
710 "Arabic - Transparent Arabic" |
||||
720 "Arabic - Transparent ASMO" |
||||
737 "OEM - Greek (formerly 437G)" |
||||
775 "OEM - Baltic" |
||||
850 "OEM - Multilingual Latin I" |
||||
852 "OEM - Latin II" |
||||
855 "OEM - Cyrillic (primarily Russian)" |
||||
857 "OEM - Turkish" |
||||
858 "OEM - Multlingual Latin I + Euro symbol" |
||||
860 "OEM - Portuguese" |
||||
861 "OEM - Icelandic" |
||||
862 "OEM - Hebrew" |
||||
863 "OEM - Canadian-French" |
||||
864 "OEM - Arabic" |
||||
865 "OEM - Nordic" |
||||
866 "OEM - Russian" |
||||
869 "OEM - Modern Greek" |
||||
870 "IBM EBCDIC - Multilingual/ROECE (Latin-2)" |
||||
874 "ANSI/OEM - Thai (same as 28605, ISO 8859-15)" |
||||
875 "IBM EBCDIC - Modern Greek" |
||||
932 "ANSI/OEM - Japanese, Shift-JIS" |
||||
936 "ANSI/OEM - Simplified Chinese (PRC, Singapore)" |
||||
949 "ANSI/OEM - Korean (Unified Hangeul Code)" |
||||
950 "ANSI/OEM - Traditional Chinese (Taiwan; Hong Kong SAR, PRC)" |
||||
1026 "IBM EBCDIC - Turkish (Latin-5)" |
||||
1047 "IBM EBCDIC - Latin 1/Open System" |
||||
1140 "IBM EBCDIC - U.S./Canada (037 + Euro symbol)" |
||||
1141 "IBM EBCDIC - Germany (20273 + Euro symbol)" |
||||
1142 "IBM EBCDIC - Denmark/Norway (20277 + Euro symbol)" |
||||
1143 "IBM EBCDIC - Finland/Sweden (20278 + Euro symbol)" |
||||
1144 "IBM EBCDIC - Italy (20280 + Euro symbol)" |
||||
1145 "IBM EBCDIC - Latin America/Spain (20284 + Euro symbol)" |
||||
1146 "IBM EBCDIC - United Kingdom (20285 + Euro symbol)" |
||||
1147 "IBM EBCDIC - France (20297 + Euro symbol)" |
||||
1148 "IBM EBCDIC - International (500 + Euro symbol)" |
||||
1149 "IBM EBCDIC - Icelandic (20871 + Euro symbol)" |
||||
1200 "Unicode UCS-2 Little-Endian (BMP of ISO 10646)" |
||||
1201 "Unicode UCS-2 Big-Endian" |
||||
1250 "ANSI - Central European" |
||||
1251 "ANSI - Cyrillic" |
||||
1252 "ANSI - Latin I" |
||||
1253 "ANSI - Greek" |
||||
1254 "ANSI - Turkish" |
||||
1255 "ANSI - Hebrew" |
||||
1256 "ANSI - Arabic" |
||||
1257 "ANSI - Baltic" |
||||
1258 "ANSI/OEM - Vietnamese" |
||||
1361 "Korean (Johab)" |
||||
10000 "MAC - Roman" |
||||
10001 "MAC - Japanese" |
||||
10002 "MAC - Traditional Chinese (Big5)" |
||||
10003 "MAC - Korean" |
||||
10004 "MAC - Arabic" |
||||
10005 "MAC - Hebrew" |
||||
10006 "MAC - Greek I" |
||||
10007 "MAC - Cyrillic" |
||||
10008 "MAC - Simplified Chinese (GB 2312)" |
||||
10010 "MAC - Romania" |
||||
10017 "MAC - Ukraine" |
||||
10021 "MAC - Thai" |
||||
10029 "MAC - Latin II" |
||||
10079 "MAC - Icelandic" |
||||
10081 "MAC - Turkish" |
||||
10082 "MAC - Croatia" |
||||
12000 "Unicode UCS-4 Little-Endian" |
||||
12001 "Unicode UCS-4 Big-Endian" |
||||
20000 "CNS - Taiwan" |
||||
20001 "TCA - Taiwan" |
||||
20002 "Eten - Taiwan" |
||||
20003 "IBM5550 - Taiwan" |
||||
20004 "TeleText - Taiwan" |
||||
20005 "Wang - Taiwan" |
||||
20105 "IA5 IRV International Alphabet No. 5 (7-bit)" |
||||
20106 "IA5 German (7-bit)" |
||||
20107 "IA5 Swedish (7-bit)" |
||||
20108 "IA5 Norwegian (7-bit)" |
||||
20127 "US-ASCII (7-bit)" |
||||
20261 "T.61" |
||||
20269 "ISO 6937 Non-Spacing Accent" |
||||
20273 "IBM EBCDIC - Germany" |
||||
20277 "IBM EBCDIC - Denmark/Norway" |
||||
20278 "IBM EBCDIC - Finland/Sweden" |
||||
20280 "IBM EBCDIC - Italy" |
||||
20284 "IBM EBCDIC - Latin America/Spain" |
||||
20285 "IBM EBCDIC - United Kingdom" |
||||
20290 "IBM EBCDIC - Japanese Katakana Extended" |
||||
20297 "IBM EBCDIC - France" |
||||
20420 "IBM EBCDIC - Arabic" |
||||
20423 "IBM EBCDIC - Greek" |
||||
20424 "IBM EBCDIC - Hebrew" |
||||
20833 "IBM EBCDIC - Korean Extended" |
||||
20838 "IBM EBCDIC - Thai" |
||||
20866 "Russian - KOI8-R" |
||||
20871 "IBM EBCDIC - Icelandic" |
||||
20880 "IBM EBCDIC - Cyrillic (Russian)" |
||||
20905 "IBM EBCDIC - Turkish" |
||||
20924 "IBM EBCDIC - Latin-1/Open System (1047 + Euro symbol)" |
||||
20932 "JIS X 0208-1990 & 0121-1990" |
||||
20936 "Simplified Chinese (GB2312)" |
||||
21025 "IBM EBCDIC - Cyrillic (Serbian, Bulgarian)" |
||||
21027 "Extended Alpha Lowercase" |
||||
21866 "Ukrainian (KOI8-U)" |
||||
28591 "ISO 8859-1 Latin I" |
||||
28592 "ISO 8859-2 Central Europe" |
||||
28593 "ISO 8859-3 Latin 3" |
||||
28594 "ISO 8859-4 Baltic" |
||||
28595 "ISO 8859-5 Cyrillic" |
||||
28596 "ISO 8859-6 Arabic" |
||||
28597 "ISO 8859-7 Greek" |
||||
28598 "ISO 8859-8 Hebrew" |
||||
28599 "ISO 8859-9 Latin 5" |
||||
28605 "ISO 8859-15 Latin 9" |
||||
29001 "Europa 3" |
||||
38598 "ISO 8859-8 Hebrew" |
||||
50220 "ISO 2022 Japanese with no halfwidth Katakana" |
||||
50221 "ISO 2022 Japanese with halfwidth Katakana" |
||||
50222 "ISO 2022 Japanese JIS X 0201-1989" |
||||
50225 "ISO 2022 Korean" |
||||
50227 "ISO 2022 Simplified Chinese" |
||||
50229 "ISO 2022 Traditional Chinese" |
||||
50930 "Japanese (Katakana) Extended" |
||||
50931 "US/Canada and Japanese" |
||||
50933 "Korean Extended and Korean" |
||||
50935 "Simplified Chinese Extended and Simplified Chinese" |
||||
50936 "Simplified Chinese" |
||||
50937 "US/Canada and Traditional Chinese" |
||||
50939 "Japanese (Latin) Extended and Japanese" |
||||
51932 "EUC - Japanese" |
||||
51936 "EUC - Simplified Chinese" |
||||
51949 "EUC - Korean" |
||||
51950 "EUC - Traditional Chinese" |
||||
52936 "HZ-GB2312 Simplified Chinese" |
||||
54936 "Windows XP: GB18030 Simplified Chinese (4 Byte)" |
||||
57002 "ISCII Devanagari" |
||||
57003 "ISCII Bengali" |
||||
57004 "ISCII Tamil" |
||||
57005 "ISCII Telugu" |
||||
57006 "ISCII Assamese" |
||||
57007 "ISCII Oriya" |
||||
57008 "ISCII Kannada" |
||||
57009 "ISCII Malayalam" |
||||
57010 "ISCII Gujarati" |
||||
57011 "ISCII Punjabi" |
||||
65000 "Unicode UTF-7" |
||||
65001 "Unicode UTF-8" |
||||
} |
||||
|
||||
# TBD - isn't there a Win32 function to do this ? |
||||
set cp [expr {0+$cp}] |
||||
if {[dict exists $code_page_names $cp]} { |
||||
return [dict get $code_page_names $cp] |
||||
} else { |
||||
return "Code page $cp" |
||||
} |
||||
} |
||||
|
||||
# |
||||
# Get the name of a language |
||||
interp alias {} twapi::map_langid_to_name {} twapi::VerLanguageName |
||||
|
||||
# |
||||
# Extract language and sublanguage values |
||||
proc twapi::extract_primary_langid {langid} { |
||||
return [expr {$langid & 0x3ff}] |
||||
} |
||||
proc twapi::extract_sublanguage_langid {langid} { |
||||
return [expr {($langid >> 10) & 0x3f}] |
||||
} |
||||
|
||||
# |
||||
# Utility functions |
||||
|
||||
proc twapi::_map_default_lcid_token {lcid} { |
||||
if {$lcid == "systemdefault"} { |
||||
return 2048 |
||||
} elseif {$lcid == "userdefault"} { |
||||
return 1024 |
||||
} |
||||
return $lcid |
||||
} |
||||
|
||||
proc twapi::_verify_number_format {n} { |
||||
set n [string trimleft $n 0] |
||||
if {[regexp {^[+-]?[[:digit:]]*(\.)?[[:digit:]]*$} $n]} { |
||||
return $n |
||||
} else { |
||||
error "Invalid numeric format. Must be of a sequence of digits with an optional decimal point and leading plus/minus sign" |
||||
} |
||||
} |
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,984 @@
|
||||
# |
||||
# Copyright (c) 2003-2014, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi { |
||||
} |
||||
|
||||
# |
||||
# Return list of toplevel performance objects |
||||
proc twapi::pdh_enumerate_objects {args} { |
||||
|
||||
array set opts [parseargs args { |
||||
datasource.arg |
||||
machine.arg |
||||
{detail.arg wizard} |
||||
refresh |
||||
} -nulldefault] |
||||
|
||||
# TBD - PdhEnumObjects enables the SeDebugPrivilege the first time it |
||||
# is called. Should we reset it if it was not already enabled? |
||||
# This seems to only happen on the first call |
||||
|
||||
return [PdhEnumObjects $opts(datasource) $opts(machine) \ |
||||
[_perf_detail_sym_to_val $opts(detail)] \ |
||||
$opts(refresh)] |
||||
} |
||||
|
||||
proc twapi::_pdh_enumerate_object_items_helper {selector objname args} { |
||||
array set opts [parseargs args { |
||||
datasource.arg |
||||
machine.arg |
||||
{detail.arg wizard} |
||||
refresh |
||||
} -nulldefault] |
||||
|
||||
if {$opts(refresh)} { |
||||
_refresh_perf_objects $opts(machine) $opts(datasource) |
||||
} |
||||
|
||||
return [PdhEnumObjectItems $opts(datasource) $opts(machine) \ |
||||
$objname \ |
||||
[_perf_detail_sym_to_val $opts(detail)] \ |
||||
$selector] |
||||
} |
||||
|
||||
interp alias {} twapi::pdh_enumerate_object_items {} twapi::_pdh_enumerate_object_items_helper 0 |
||||
interp alias {} twapi::pdh_enumerate_object_counters {} twapi::_pdh_enumerate_object_items_helper 1 |
||||
interp alias {} twapi::pdh_enumerate_object_instances {} twapi::_pdh_enumerate_object_items_helper 2 |
||||
|
||||
|
||||
# |
||||
# Construct a counter path |
||||
proc twapi::pdh_counter_path {object counter args} { |
||||
array set opts [parseargs args { |
||||
machine.arg |
||||
instance.arg |
||||
parent.arg |
||||
{instanceindex.int -1} |
||||
{localized.bool false} |
||||
} -nulldefault] |
||||
|
||||
if {$opts(instanceindex) == 0} { |
||||
# For XP. For first instance (index 0), the path should not contain |
||||
# "#0" but on XP it does. Reset it to -1 for Vista+ consistency |
||||
set opts(instanceindex) -1 |
||||
} |
||||
|
||||
|
||||
if {! $opts(localized)} { |
||||
# Need to localize the counter names |
||||
set object [_pdh_localize $object] |
||||
set counter [_pdh_localize $counter] |
||||
# TBD - not sure we need to localize parent |
||||
set opts(parent) [_pdh_localize $opts(parent)] |
||||
} |
||||
|
||||
# TBD - add options PDH_PATH_WBEM as documented in PdhMakeCounterPath |
||||
return [PdhMakeCounterPath $opts(machine) $object $opts(instance) \ |
||||
$opts(parent) $opts(instanceindex) $counter 0] |
||||
|
||||
} |
||||
|
||||
# |
||||
# Parse a counter path and return the individual elements |
||||
proc twapi::pdh_parse_counter_path {counter_path} { |
||||
return [twine {machine object instance parent instanceindex counter} [PdhParseCounterPath $counter_path 0]] |
||||
} |
||||
|
||||
|
||||
interp alias {} twapi::pdh_get_scalar {} twapi::_pdh_get 1 |
||||
interp alias {} twapi::pdh_get_array {} twapi::_pdh_get 0 |
||||
|
||||
proc twapi::_pdh_get {scalar hcounter args} { |
||||
|
||||
array set opts [parseargs args { |
||||
{format.arg large {long large double}} |
||||
{scale.arg {} {{} none x1000 nocap100}} |
||||
var.arg |
||||
} -ignoreunknown -nulldefault] |
||||
|
||||
set flags [_pdh_fmt_sym_to_val $opts(format)] |
||||
|
||||
if {$opts(scale) ne ""} { |
||||
set flags [expr {$flags | [_pdh_fmt_sym_to_val $opts(scale)]}] |
||||
} |
||||
|
||||
set status 1 |
||||
set result "" |
||||
trap { |
||||
if {$scalar} { |
||||
set result [PdhGetFormattedCounterValue $hcounter $flags] |
||||
} else { |
||||
set result [PdhGetFormattedCounterArray $hcounter $flags] |
||||
} |
||||
} onerror {TWAPI_WIN32 0x800007d1} { |
||||
# Error is that no such instance exists. |
||||
# If result is being returned in a variable, then |
||||
# we will not generate an error but pass back a return value |
||||
# of 0 |
||||
if {[string length $opts(var)] == 0} { |
||||
rethrow |
||||
} |
||||
set status 0 |
||||
} |
||||
|
||||
if {[string length $opts(var)]} { |
||||
uplevel [list set $opts(var) $result] |
||||
return $status |
||||
} else { |
||||
return $result |
||||
} |
||||
} |
||||
|
||||
# |
||||
# Get the value of a counter identified by the path. |
||||
# Should not be used to collect |
||||
# rate based options. |
||||
# TBD - document |
||||
proc twapi::pdh_counter_path_value {counter_path args} { |
||||
|
||||
array set opts [parseargs args { |
||||
{format.arg long} |
||||
scale.arg |
||||
datasource.arg |
||||
var.arg |
||||
full.bool |
||||
} -nulldefault] |
||||
|
||||
# Open the query |
||||
set hquery [pdh_query_open -datasource $opts(datasource)] |
||||
trap { |
||||
set hcounter [pdh_add_counter $hquery $counter_path] |
||||
pdh_query_refresh $hquery |
||||
if {[string length $opts(var)]} { |
||||
# Need to pass up value in a variable if so requested |
||||
upvar $opts(var) myvar |
||||
set opts(var) myvar |
||||
} |
||||
set value [pdh_get_scalar $hcounter -format $opts(format) \ |
||||
-scale $opts(scale) -full $opts(full) \ |
||||
-var $opts(var)] |
||||
} finally { |
||||
pdh_query_close $hquery |
||||
} |
||||
|
||||
return $value |
||||
} |
||||
|
||||
|
||||
# |
||||
# Constructs one or more counter paths for getting process information. |
||||
# Returned as a list of sublists. Each sublist corresponds to a counter path |
||||
# and has the form {counteroptionname datatype counterpath rate} |
||||
# datatype is the recommended format when retrieving counter value (eg. double) |
||||
# rate is 0 or 1 depending on whether the counter is a rate based counter or |
||||
# not (requires at least two readings when getting the value) |
||||
proc twapi::get_perf_process_counter_paths {pids args} { |
||||
variable _process_counter_opt_map |
||||
|
||||
if {![info exists _counter_opt_map]} { |
||||
# "descriptive string" format rate |
||||
array set _process_counter_opt_map { |
||||
privilegedutilization {"% Privileged Time" double 1} |
||||
processorutilization {"% Processor Time" double 1} |
||||
userutilization {"% User Time" double 1} |
||||
parent {"Creating Process ID" long 0} |
||||
elapsedtime {"Elapsed Time" large 0} |
||||
handlecount {"Handle Count" long 0} |
||||
pid {"ID Process" long 0} |
||||
iodatabytesrate {"IO Data Bytes/sec" large 1} |
||||
iodataopsrate {"IO Data Operations/sec" large 1} |
||||
iootherbytesrate {"IO Other Bytes/sec" large 1} |
||||
iootheropsrate {"IO Other Operations/sec" large 1} |
||||
ioreadbytesrate {"IO Read Bytes/sec" large 1} |
||||
ioreadopsrate {"IO Read Operations/sec" large 1} |
||||
iowritebytesrate {"IO Write Bytes/sec" large 1} |
||||
iowriteopsrate {"IO Write Operations/sec" large 1} |
||||
pagefaultrate {"Page Faults/sec" large 1} |
||||
pagefilebytes {"Page File Bytes" large 0} |
||||
pagefilebytespeak {"Page File Bytes Peak" large 0} |
||||
poolnonpagedbytes {"Pool Nonpaged Bytes" large 0} |
||||
poolpagedbytes {"Pool Paged Bytes" large 1} |
||||
basepriority {"Priority Base" large 1} |
||||
privatebytes {"Private Bytes" large 1} |
||||
threadcount {"Thread Count" large 1} |
||||
virtualbytes {"Virtual Bytes" large 1} |
||||
virtualbytespeak {"Virtual Bytes Peak" large 1} |
||||
workingset {"Working Set" large 1} |
||||
workingsetpeak {"Working Set Peak" large 1} |
||||
} |
||||
} |
||||
|
||||
set optdefs { |
||||
machine.arg |
||||
datasource.arg |
||||
all |
||||
refresh |
||||
} |
||||
|
||||
# Add counter names to option list |
||||
foreach cntr [array names _process_counter_opt_map] { |
||||
lappend optdefs $cntr |
||||
} |
||||
|
||||
# Parse options |
||||
array set opts [parseargs args $optdefs -nulldefault] |
||||
|
||||
# Force a refresh of object items |
||||
if {$opts(refresh)} { |
||||
# Silently ignore. The above counters are predefined and refreshing |
||||
# is just a time-consuming no-op. Keep the option for backward |
||||
# compatibility |
||||
if {0} { |
||||
_refresh_perf_objects $opts(machine) $opts(datasource) |
||||
} |
||||
} |
||||
|
||||
# TBD - could we not use get_perf_instance_counter_paths instead of rest of this code |
||||
|
||||
# Get the path to the process. |
||||
set pid_paths [get_perf_counter_paths \ |
||||
[_pdh_localize "Process"] \ |
||||
[list [_pdh_localize "ID Process"]] \ |
||||
$pids \ |
||||
-machine $opts(machine) -datasource $opts(datasource) \ |
||||
-all] |
||||
|
||||
if {[llength $pid_paths] == 0} { |
||||
# No thread |
||||
return [list ] |
||||
} |
||||
|
||||
# Construct the requested counter paths |
||||
set counter_paths [list ] |
||||
foreach {pid pid_path} $pid_paths { |
||||
|
||||
# We have to filter out an entry for _Total which might be present |
||||
# if pid includes "0" |
||||
# TBD - does _Total need to be localized? |
||||
if {$pid == 0 && [string match -nocase *_Total\#0* $pid_path]} { |
||||
continue |
||||
} |
||||
|
||||
# Break it down into components and store in array |
||||
array set path_components [pdh_parse_counter_path $pid_path] |
||||
|
||||
# Construct counter paths for this pid |
||||
foreach {opt counter_info} [array get _process_counter_opt_map] { |
||||
if {$opts(all) || $opts($opt)} { |
||||
lappend counter_paths \ |
||||
[list -$opt $pid [lindex $counter_info 1] \ |
||||
[pdh_counter_path $path_components(object) \ |
||||
[_pdh_localize [lindex $counter_info 0]] \ |
||||
-localized true \ |
||||
-machine $path_components(machine) \ |
||||
-parent $path_components(parent) \ |
||||
-instance $path_components(instance) \ |
||||
-instanceindex $path_components(instanceindex)] \ |
||||
[lindex $counter_info 2] \ |
||||
] |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $counter_paths |
||||
} |
||||
|
||||
|
||||
# Returns the counter path for the process with the given pid. This includes |
||||
# the pid counter path element |
||||
proc twapi::get_perf_process_id_path {pid args} { |
||||
return [get_unique_counter_path \ |
||||
[_pdh_localize "Process"] \ |
||||
[_pdh_localize "ID Process"] $pid] |
||||
} |
||||
|
||||
|
||||
# |
||||
# Constructs one or more counter paths for getting thread information. |
||||
# Returned as a list of sublists. Each sublist corresponds to a counter path |
||||
# and has the form {counteroptionname datatype counterpath rate} |
||||
# datatype is the recommended format when retrieving counter value (eg. double) |
||||
# rate is 0 or 1 depending on whether the counter is a rate based counter or |
||||
# not (requires at least two readings when getting the value) |
||||
proc twapi::get_perf_thread_counter_paths {tids args} { |
||||
variable _thread_counter_opt_map |
||||
|
||||
if {![info exists _thread_counter_opt_map]} { |
||||
array set _thread_counter_opt_map { |
||||
privilegedutilization {"% Privileged Time" double 1} |
||||
processorutilization {"% Processor Time" double 1} |
||||
userutilization {"% User Time" double 1} |
||||
contextswitchrate {"Context Switches/sec" long 1} |
||||
elapsedtime {"Elapsed Time" large 0} |
||||
pid {"ID Process" long 0} |
||||
tid {"ID Thread" long 0} |
||||
basepriority {"Priority Base" long 0} |
||||
priority {"Priority Current" long 0} |
||||
startaddress {"Start Address" large 0} |
||||
state {"Thread State" long 0} |
||||
waitreason {"Thread Wait Reason" long 0} |
||||
} |
||||
} |
||||
|
||||
set optdefs { |
||||
machine.arg |
||||
datasource.arg |
||||
all |
||||
refresh |
||||
} |
||||
|
||||
# Add counter names to option list |
||||
foreach cntr [array names _thread_counter_opt_map] { |
||||
lappend optdefs $cntr |
||||
} |
||||
|
||||
# Parse options |
||||
array set opts [parseargs args $optdefs -nulldefault] |
||||
|
||||
# Force a refresh of object items |
||||
if {$opts(refresh)} { |
||||
# Silently ignore. The above counters are predefined and refreshing |
||||
# is just a time-consuming no-op. Keep the option for backward |
||||
# compatibility |
||||
if {0} { |
||||
_refresh_perf_objects $opts(machine) $opts(datasource) |
||||
} |
||||
} |
||||
|
||||
# TBD - could we not use get_perf_instance_counter_paths instead of rest of this code |
||||
|
||||
# Get the path to the thread |
||||
set tid_paths [get_perf_counter_paths \ |
||||
[_pdh_localize "Thread"] \ |
||||
[list [_pdh_localize "ID Thread"]] \ |
||||
$tids \ |
||||
-machine $opts(machine) -datasource $opts(datasource) \ |
||||
-all] |
||||
|
||||
if {[llength $tid_paths] == 0} { |
||||
# No thread |
||||
return [list ] |
||||
} |
||||
|
||||
# Now construct the requested counter paths |
||||
set counter_paths [list ] |
||||
foreach {tid tid_path} $tid_paths { |
||||
# Break it down into components and store in array |
||||
array set path_components [pdh_parse_counter_path $tid_path] |
||||
foreach {opt counter_info} [array get _thread_counter_opt_map] { |
||||
if {$opts(all) || $opts($opt)} { |
||||
lappend counter_paths \ |
||||
[list -$opt $tid [lindex $counter_info 1] \ |
||||
[pdh_counter_path $path_components(object) \ |
||||
[_pdh_localize [lindex $counter_info 0]] \ |
||||
-localized true \ |
||||
-machine $path_components(machine) \ |
||||
-parent $path_components(parent) \ |
||||
-instance $path_components(instance) \ |
||||
-instanceindex $path_components(instanceindex)] \ |
||||
[lindex $counter_info 2] |
||||
] |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $counter_paths |
||||
} |
||||
|
||||
|
||||
# Returns the counter path for the thread with the given tid. This includes |
||||
# the tid counter path element |
||||
proc twapi::get_perf_thread_id_path {tid args} { |
||||
|
||||
return [get_unique_counter_path [_pdh_localize"Thread"] [_pdh_localize "ID Thread"] $tid] |
||||
} |
||||
|
||||
|
||||
# |
||||
# Constructs one or more counter paths for getting processor information. |
||||
# Returned as a list of sublists. Each sublist corresponds to a counter path |
||||
# and has the form {counteroptionname datatype counterpath rate} |
||||
# datatype is the recommended format when retrieving counter value (eg. double) |
||||
# rate is 0 or 1 depending on whether the counter is a rate based counter or |
||||
# not (requires at least two readings when getting the value) |
||||
# $processor should be the processor number or "" to get total |
||||
proc twapi::get_perf_processor_counter_paths {processor args} { |
||||
variable _processor_counter_opt_map |
||||
|
||||
if {![string is integer -strict $processor]} { |
||||
if {[string length $processor]} { |
||||
error "Processor id must be an integer or null to retrieve information for all processors" |
||||
} |
||||
set processor "_Total" |
||||
} |
||||
|
||||
if {![info exists _processor_counter_opt_map]} { |
||||
array set _processor_counter_opt_map { |
||||
dpcutilization {"% DPC Time" double 1} |
||||
interruptutilization {"% Interrupt Time" double 1} |
||||
privilegedutilization {"% Privileged Time" double 1} |
||||
processorutilization {"% Processor Time" double 1} |
||||
userutilization {"% User Time" double 1} |
||||
dpcrate {"DPC Rate" double 1} |
||||
dpcqueuerate {"DPCs Queued/sec" double 1} |
||||
interruptrate {"Interrupts/sec" double 1} |
||||
} |
||||
} |
||||
|
||||
set optdefs { |
||||
machine.arg |
||||
datasource.arg |
||||
all |
||||
refresh |
||||
} |
||||
|
||||
# Add counter names to option list |
||||
foreach cntr [array names _processor_counter_opt_map] { |
||||
lappend optdefs $cntr |
||||
} |
||||
|
||||
# Parse options |
||||
array set opts [parseargs args $optdefs -nulldefault -maxleftover 0] |
||||
|
||||
# Force a refresh of object items |
||||
if {$opts(refresh)} { |
||||
# Silently ignore. The above counters are predefined and refreshing |
||||
# is just a time-consuming no-op. Keep the option for backward |
||||
# compatibility |
||||
if {0} { |
||||
_refresh_perf_objects $opts(machine) $opts(datasource) |
||||
} |
||||
} |
||||
|
||||
# Now construct the requested counter paths |
||||
set counter_paths [list ] |
||||
foreach {opt counter_info} [array get _processor_counter_opt_map] { |
||||
if {$opts(all) || $opts($opt)} { |
||||
lappend counter_paths \ |
||||
[list $opt $processor [lindex $counter_info 1] \ |
||||
[pdh_counter_path \ |
||||
[_pdh_localize "Processor"] \ |
||||
[_pdh_localize [lindex $counter_info 0]] \ |
||||
-localized true \ |
||||
-machine $opts(machine) \ |
||||
-instance $processor] \ |
||||
[lindex $counter_info 2] \ |
||||
] |
||||
} |
||||
} |
||||
|
||||
return $counter_paths |
||||
} |
||||
|
||||
|
||||
|
||||
# |
||||
# Returns a list comprising of the counter paths for counters with |
||||
# names in the list $counters from those instance(s) whose counter |
||||
# $key_counter matches the specified $key_counter_value |
||||
proc twapi::get_perf_instance_counter_paths {object counters |
||||
key_counter key_counter_values |
||||
args} { |
||||
# Parse options |
||||
array set opts [parseargs args { |
||||
machine.arg |
||||
datasource.arg |
||||
{matchop.arg "exact"} |
||||
skiptotal.bool |
||||
refresh |
||||
} -nulldefault] |
||||
|
||||
# Force a refresh of object items |
||||
if {$opts(refresh)} { |
||||
_refresh_perf_objects $opts(machine) $opts(datasource) |
||||
} |
||||
|
||||
# Get the list of instances that have the specified value for the |
||||
# key counter |
||||
set instance_paths [get_perf_counter_paths $object \ |
||||
[list $key_counter] $key_counter_values \ |
||||
-machine $opts(machine) \ |
||||
-datasource $opts(datasource) \ |
||||
-matchop $opts(matchop) \ |
||||
-skiptotal $opts(skiptotal) \ |
||||
-all] |
||||
|
||||
# Loop through all instance paths, and all counters to generate |
||||
# We store in an array to get rid of duplicates |
||||
array set counter_paths {} |
||||
foreach {key_counter_value instance_path} $instance_paths { |
||||
# Break it down into components and store in array |
||||
array set path_components [pdh_parse_counter_path $instance_path] |
||||
|
||||
# Now construct the requested counter paths |
||||
# TBD - what should -localized be here ? |
||||
foreach counter $counters { |
||||
set counter_path \ |
||||
[pdh_counter_path $path_components(object) \ |
||||
$counter \ |
||||
-localized true \ |
||||
-machine $path_components(machine) \ |
||||
-parent $path_components(parent) \ |
||||
-instance $path_components(instance) \ |
||||
-instanceindex $path_components(instanceindex)] |
||||
set counter_paths($counter_path) "" |
||||
} |
||||
} |
||||
|
||||
return [array names counter_paths] |
||||
|
||||
|
||||
} |
||||
|
||||
|
||||
# |
||||
# Returns a list comprising of the counter paths for all counters |
||||
# whose values match the specified criteria |
||||
proc twapi::get_perf_counter_paths {object counters counter_values args} { |
||||
array set opts [parseargs args { |
||||
machine.arg |
||||
datasource.arg |
||||
{matchop.arg "exact"} |
||||
skiptotal.bool |
||||
all |
||||
refresh |
||||
} -nulldefault] |
||||
|
||||
if {$opts(refresh)} { |
||||
_refresh_perf_objects $opts(machine) $opts(datasource) |
||||
} |
||||
|
||||
set items [pdh_enum_object_items $object \ |
||||
-machine $opts(machine) \ |
||||
-datasource $opts(datasource)] |
||||
lassign $items object_counters object_instances |
||||
|
||||
if {[llength $counters]} { |
||||
set object_counters $counters |
||||
} |
||||
set paths [_make_counter_path_list \ |
||||
$object $object_instances $object_counters \ |
||||
-skiptotal $opts(skiptotal) -machine $opts(machine)] |
||||
set result_paths [list ] |
||||
trap { |
||||
# Set up the query with the process id for all processes |
||||
set hquery [pdh_query_open -datasource $opts(datasource)] |
||||
foreach path $paths { |
||||
set hcounter [pdh_add_counter $hquery $path] |
||||
set lookup($hcounter) $path |
||||
} |
||||
|
||||
# Now collect the info |
||||
pdh_query_refresh $hquery |
||||
|
||||
# Now lookup each counter value to find a matching one |
||||
foreach hcounter [array names lookup] { |
||||
if {! [pdh_get_scalar $hcounter -var value]} { |
||||
# Counter or instance no longer exists |
||||
continue |
||||
} |
||||
|
||||
set match_pos [lsearch -$opts(matchop) $counter_values $value] |
||||
if {$match_pos >= 0} { |
||||
lappend result_paths \ |
||||
[lindex $counter_values $match_pos] $lookup($hcounter) |
||||
if {! $opts(all)} { |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} finally { |
||||
# TBD - should we have a catch to throw errors? |
||||
pdh_query_close $hquery |
||||
} |
||||
|
||||
return $result_paths |
||||
} |
||||
|
||||
|
||||
# |
||||
# Returns the counter path for counter $counter with a value $value |
||||
# for object $object. Returns "" on no matches but exception if more than one |
||||
proc twapi::get_unique_counter_path {object counter value args} { |
||||
set matches [get_perf_counter_paths $object [list $counter ] [list $value] {*}$args -all] |
||||
if {[llength $matches] > 1} { |
||||
error "Multiple counter paths found matching criteria object='$object' counter='$counter' value='$value" |
||||
} |
||||
return [lindex $matches 0] |
||||
} |
||||
|
||||
|
||||
|
||||
# |
||||
# Utilities |
||||
# |
||||
proc twapi::_refresh_perf_objects {machine datasource} { |
||||
pdh_enumerate_objects -refresh |
||||
return |
||||
} |
||||
|
||||
|
||||
# |
||||
# Return the localized form of a counter name |
||||
# TBD - assumes machine is local machine! |
||||
proc twapi::_pdh_localize {name} { |
||||
variable _perf_counter_ids |
||||
variable _localized_perf_counter_names |
||||
|
||||
set name_index [string tolower $name] |
||||
|
||||
# If we already have a translation, return it |
||||
if {[info exists _localized_perf_counter_names($name_index)]} { |
||||
return $_localized_perf_counter_names($name_index) |
||||
} |
||||
|
||||
# Didn't already have it. Go generate the mappings |
||||
|
||||
# Get the list of counter names in English if we don't already have it |
||||
if {![info exists _perf_counter_ids]} { |
||||
foreach {id label} [registry get {HKEY_PERFORMANCE_DATA} {Counter 009}] { |
||||
set _perf_counter_ids([string tolower $label]) $id |
||||
} |
||||
} |
||||
|
||||
# If we have do not have id for the given name, we will just use |
||||
# the passed name as the localized version |
||||
if {! [info exists _perf_counter_ids($name_index)]} { |
||||
# Does not seem to exist. Just set localized name to itself |
||||
return [set _localized_perf_counter_names($name_index) $name] |
||||
} |
||||
|
||||
# We do have an id. THen try to get a translated name |
||||
if {[catch {PdhLookupPerfNameByIndex "" $_perf_counter_ids($name_index)} xname]} { |
||||
set _localized_perf_counter_names($name_index) $name |
||||
} else { |
||||
set _localized_perf_counter_names($name_index) $xname |
||||
} |
||||
|
||||
return $_localized_perf_counter_names($name_index) |
||||
} |
||||
|
||||
|
||||
# Given a list of instances and counters, return a cross product of the |
||||
# corresponding counter paths. |
||||
# The list is expected to be already localized |
||||
# Example: _make_counter_path_list "Process" (instance list) {{ID Process} {...}} |
||||
# TBD - bug - does not handle -parent in counter path |
||||
proc twapi::_make_counter_path_list {object instance_list counter_list args} { |
||||
array set opts [parseargs args { |
||||
machine.arg |
||||
skiptotal.bool |
||||
} -nulldefault] |
||||
|
||||
array set instances {} |
||||
foreach instance $instance_list { |
||||
if {![info exists instances($instance)]} { |
||||
set instances($instance) 1 |
||||
} else { |
||||
incr instances($instance) |
||||
} |
||||
} |
||||
|
||||
if {$opts(skiptotal)} { |
||||
catch {array unset instances "*_Total"} |
||||
} |
||||
|
||||
set counter_paths [list ] |
||||
foreach {instance count} [array get instances] { |
||||
while {$count} { |
||||
incr count -1 |
||||
foreach counter $counter_list { |
||||
lappend counter_paths [pdh_counter_path \ |
||||
$object $counter \ |
||||
-localized true \ |
||||
-machine $opts(machine) \ |
||||
-instance $instance \ |
||||
-instanceindex $count] |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $counter_paths |
||||
} |
||||
|
||||
|
||||
# |
||||
# Given a set of counter paths in the format returned by |
||||
# get_perf_thread_counter_paths, get_perf_processor_counter_paths etc. |
||||
# return the counter information as a flat list of field value pairs |
||||
proc twapi::get_perf_values_from_metacounter_info {metacounters args} { |
||||
array set opts [parseargs args {{interval.int 100}}] |
||||
|
||||
set result [list ] |
||||
set counters [list ] |
||||
if {[llength $metacounters]} { |
||||
set hquery [pdh_query_open] |
||||
trap { |
||||
set counter_info [list ] |
||||
set need_wait 0 |
||||
foreach counter_elem $metacounters { |
||||
lassign $counter_elem pdh_opt key data_type counter_path wait |
||||
incr need_wait $wait |
||||
set hcounter [pdh_add_counter $hquery $counter_path] |
||||
lappend counters $hcounter |
||||
lappend counter_info $pdh_opt $key $counter_path $data_type $hcounter |
||||
} |
||||
|
||||
pdh_query_refresh $hquery |
||||
if {$need_wait} { |
||||
after $opts(interval) |
||||
pdh_query_refresh $hquery |
||||
} |
||||
|
||||
foreach {pdh_opt key counter_path data_type hcounter} $counter_info { |
||||
if {[pdh_get_scalar $hcounter -format $data_type -var value]} { |
||||
lappend result $pdh_opt $key $value |
||||
} |
||||
} |
||||
} onerror {} { |
||||
#puts "Error: $msg" |
||||
} finally { |
||||
pdh_query_close $hquery |
||||
} |
||||
} |
||||
|
||||
return $result |
||||
|
||||
} |
||||
|
||||
proc twapi::pdh_query_open {args} { |
||||
variable _pdh_queries |
||||
|
||||
array set opts [parseargs args { |
||||
datasource.arg |
||||
cookie.int |
||||
} -nulldefault] |
||||
|
||||
set qh [PdhOpenQuery $opts(datasource) $opts(cookie)] |
||||
set id pdh[TwapiId] |
||||
dict set _pdh_queries($id) Qh $qh |
||||
dict set _pdh_queries($id) Counters {} |
||||
dict set _pdh_queries($id) Meta {} |
||||
return $id |
||||
} |
||||
|
||||
proc twapi::pdh_query_refresh {qid args} { |
||||
variable _pdh_queries |
||||
_pdh_query_check $qid |
||||
PdhCollectQueryData [dict get $_pdh_queries($qid) Qh] |
||||
return |
||||
} |
||||
|
||||
proc twapi::pdh_query_close {qid} { |
||||
variable _pdh_queries |
||||
_pdh_query_check $qid |
||||
|
||||
dict for {ctrh -} [dict get $_pdh_queries($qid) Counters] { |
||||
PdhRemoveCounter $ctrh |
||||
} |
||||
|
||||
PdhCloseQuery [dict get $_pdh_queries($qid) Qh] |
||||
unset _pdh_queries($qid) |
||||
} |
||||
|
||||
proc twapi::pdh_add_counter {qid ctr_path args} { |
||||
variable _pdh_queries |
||||
|
||||
_pdh_query_check $qid |
||||
|
||||
parseargs args { |
||||
{format.arg large {long large double}} |
||||
{scale.arg {} {{} none x1000 nocap100}} |
||||
name.arg |
||||
cookie.int |
||||
array.bool |
||||
} -nulldefault -maxleftover 0 -setvars |
||||
|
||||
if {$name eq ""} { |
||||
set name $ctr_path |
||||
} |
||||
|
||||
if {[dict exists $_pdh_queries($qid) Meta $name]} { |
||||
error "A counter with name \"$name\" already present in the query." |
||||
} |
||||
|
||||
set flags [_pdh_fmt_sym_to_val $format] |
||||
|
||||
if {$scale ne ""} { |
||||
set flags [expr {$flags | [_pdh_fmt_sym_to_val $scale]}] |
||||
} |
||||
|
||||
set hctr [PdhAddCounter [dict get $_pdh_queries($qid) Qh] $ctr_path $flags] |
||||
dict set _pdh_queries($qid) Counters $hctr 1 |
||||
dict set _pdh_queries($qid) Meta $name [list Counter $hctr FmtFlags $flags Array $array] |
||||
|
||||
return $hctr |
||||
} |
||||
|
||||
proc twapi::pdh_remove_counter {qid ctrname} { |
||||
variable _pdh_queries |
||||
_pdh_query_check $qid |
||||
if {![dict exists $_pdh_queries($qid) Meta $ctrname]} { |
||||
badargs! "Counter \"$ctrname\" not present in query." |
||||
} |
||||
set hctr [dict get $_pdh_queries($qid) Meta $ctrname Counter] |
||||
dict unset _pdh_queries($qid) Counters $hctr |
||||
dict unset _pdh_queries($qid) Meta $ctrname |
||||
PdhRemoveCounter $hctr |
||||
return |
||||
} |
||||
|
||||
proc twapi::pdh_query_get {qid args} { |
||||
variable _pdh_queries |
||||
|
||||
_pdh_query_check $qid |
||||
|
||||
# Refresh the data |
||||
PdhCollectQueryData [dict get $_pdh_queries($qid) Qh] |
||||
|
||||
set meta [dict get $_pdh_queries($qid) Meta] |
||||
|
||||
if {[llength $args] != 0} { |
||||
set names $args |
||||
} else { |
||||
set names [dict keys $meta] |
||||
} |
||||
|
||||
set result {} |
||||
foreach name $names { |
||||
if {[dict get $meta $name Array]} { |
||||
lappend result $name [PdhGetFormattedCounterArray [dict get $meta $name Counter] [dict get $meta $name FmtFlags]] |
||||
} else { |
||||
lappend result $name [PdhGetFormattedCounterValue [dict get $meta $name Counter] [dict get $meta $name FmtFlags]] |
||||
} |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
twapi::proc* twapi::pdh_system_performance_query args { |
||||
variable _sysperf_defs |
||||
|
||||
set _sysperf_defs { |
||||
event_count { {Objects Events} {} } |
||||
mutex_count { {Objects Mutexes} {} } |
||||
process_count { {Objects Processes} {} } |
||||
section_count { {Objects Sections} {} } |
||||
semaphore_count { {Objects Semaphores} {} } |
||||
thread_count { {Objects Threads} {} } |
||||
handle_count { {Process "Handle Count" -instance _Total} {-format long} } |
||||
commit_limit { {Memory "Commit Limit"} {} } |
||||
committed_bytes { {Memory "Committed Bytes"} {} } |
||||
committed_percent { {Memory "% Committed Bytes In Use"} {-format double} } |
||||
memory_free_mb { {Memory "Available MBytes"} {} } |
||||
memory_free_kb { {Memory "Available KBytes"} {} } |
||||
page_fault_rate { {Memory "Page Faults/sec"} {} } |
||||
page_input_rate { {Memory "Pages Input/sec"} {} } |
||||
page_output_rate { {Memory "Pages Output/sec"} {} } |
||||
|
||||
disk_bytes_rate { {PhysicalDisk "Disk Bytes/sec" -instance _Total} {} } |
||||
disk_readbytes_rate { {PhysicalDisk "Disk Read Bytes/sec" -instance _Total} {} } |
||||
disk_writebytes_rate { {PhysicalDisk "Disk Write Bytes/sec" -instance _Total} {} } |
||||
disk_transfer_rate { {PhysicalDisk "Disk Transfers/sec" -instance _Total} {} } |
||||
disk_read_rate { {PhysicalDisk "Disk Reads/sec" -instance _Total} {} } |
||||
disk_write_rate { {PhysicalDisk "Disk Writes/sec" -instance _Total} {} } |
||||
disk_idle_percent { {PhysicalDisk "% Idle Time" -instance _Total} {-format double} } |
||||
} |
||||
|
||||
# Per-processor counters are based on above but the object name depends |
||||
# on the system in order to support > 64 processors |
||||
set obj_name [expr {[min_os_version 6 1] ? "Processor Information" : "Processor"}] |
||||
dict for {key ctr_name} { |
||||
interrupt_utilization "% Interrupt Time" |
||||
privileged_utilization "% Privileged Time" |
||||
processor_utilization "% Processor Time" |
||||
user_utilization "% User Time" |
||||
idle_utilization "% Idle Time" |
||||
} { |
||||
lappend _sysperf_defs $key \ |
||||
[list \ |
||||
[list $obj_name $ctr_name -instance _Total] \ |
||||
[list -format double]] |
||||
|
||||
lappend _sysperf_defs ${key}_per_cpu \ |
||||
[list \ |
||||
[list $obj_name $ctr_name -instance *] \ |
||||
[list -format double -array 1]] |
||||
} |
||||
} { |
||||
variable _sysperf_defs |
||||
|
||||
if {[llength $args] == 0} { |
||||
return [lsort -dictionary [dict keys $_sysperf_defs]] |
||||
} |
||||
|
||||
set qid [pdh_query_open] |
||||
trap { |
||||
foreach arg $args { |
||||
set def [dict! $_sysperf_defs $arg] |
||||
set ctr_path [pdh_counter_path {*}[lindex $def 0]] |
||||
pdh_add_counter $qid $ctr_path -name $arg {*}[lindex $def 1] |
||||
} |
||||
pdh_query_refresh $qid |
||||
} onerror {} { |
||||
pdh_query_close $qid |
||||
rethrow |
||||
} |
||||
|
||||
return $qid |
||||
} |
||||
|
||||
# |
||||
# Internal utility procedures |
||||
proc twapi::_pdh_query_check {qid} { |
||||
variable _pdh_queries |
||||
|
||||
if {![info exists _pdh_queries($qid)]} { |
||||
error "Invalid query id $qid" |
||||
} |
||||
} |
||||
|
||||
proc twapi::_perf_detail_sym_to_val {sym} { |
||||
# PERF_DETAIL_NOVICE 100 |
||||
# PERF_DETAIL_ADVANCED 200 |
||||
# PERF_DETAIL_EXPERT 300 |
||||
# PERF_DETAIL_WIZARD 400 |
||||
# PERF_DETAIL_COSTLY 0x00010000 |
||||
# PERF_DETAIL_STANDARD 0x0000FFFF |
||||
|
||||
return [dict get {novice 100 advanced 200 expert 300 wizard 400 costly 0x00010000 standard 0x0000ffff } $sym] |
||||
} |
||||
|
||||
|
||||
proc twapi::_pdh_fmt_sym_to_val {sym} { |
||||
# PDH_FMT_RAW 0x00000010 |
||||
# PDH_FMT_ANSI 0x00000020 |
||||
# PDH_FMT_UNICODE 0x00000040 |
||||
# PDH_FMT_LONG 0x00000100 |
||||
# PDH_FMT_DOUBLE 0x00000200 |
||||
# PDH_FMT_LARGE 0x00000400 |
||||
# PDH_FMT_NOSCALE 0x00001000 |
||||
# PDH_FMT_1000 0x00002000 |
||||
# PDH_FMT_NODATA 0x00004000 |
||||
# PDH_FMT_NOCAP100 0x00008000 |
||||
|
||||
return [dict get { |
||||
raw 0x00000010 |
||||
ansi 0x00000020 |
||||
unicode 0x00000040 |
||||
long 0x00000100 |
||||
double 0x00000200 |
||||
large 0x00000400 |
||||
noscale 0x00001000 |
||||
none 0x00001000 |
||||
1000 0x00002000 |
||||
x1000 0x00002000 |
||||
nodata 0x00004000 |
||||
nocap100 0x00008000 |
||||
nocap 0x00008000 |
||||
} $sym] |
||||
} |
@ -0,0 +1,119 @@
|
||||
# |
||||
# Tcl package index file |
||||
# |
||||
|
||||
namespace eval twapi { |
||||
variable scriptdir |
||||
proc set_scriptdir dir {variable scriptdir ; set scriptdir $dir} |
||||
} |
||||
|
||||
package ifneeded twapi_base 4.7.2 \ |
||||
[list load [file join $dir twapi472.dll] twapi_base] |
||||
package ifneeded twapi_com 4.7.2 \ |
||||
{load {} twapi_com} |
||||
package ifneeded metoo 4.7.2 \ |
||||
[list source [file join $dir metoo.tcl]] |
||||
package ifneeded twapi_com 4.7.2 \ |
||||
{load {} twapi_com} |
||||
package ifneeded twapi_msi 4.7.2 \ |
||||
[list source [file join $dir msi.tcl]] |
||||
package ifneeded twapi_power 4.7.2 \ |
||||
[list source [file join $dir power.tcl]] |
||||
package ifneeded twapi_printer 4.7.2 \ |
||||
[list source [file join $dir printer.tcl]] |
||||
package ifneeded twapi_synch 4.7.2 \ |
||||
[list source [file join $dir synch.tcl]] |
||||
package ifneeded twapi_security 4.7.2 \ |
||||
{load {} twapi_security} |
||||
package ifneeded twapi_account 4.7.2 \ |
||||
{load {} twapi_account} |
||||
package ifneeded twapi_apputil 4.7.2 \ |
||||
{load {} twapi_apputil} |
||||
package ifneeded twapi_clipboard 4.7.2 \ |
||||
{load {} twapi_clipboard} |
||||
package ifneeded twapi_console 4.7.2 \ |
||||
{load {} twapi_console} |
||||
package ifneeded twapi_crypto 4.7.2 \ |
||||
{load {} twapi_crypto} |
||||
package ifneeded twapi_device 4.7.2 \ |
||||
{load {} twapi_device} |
||||
package ifneeded twapi_etw 4.7.2 \ |
||||
{load {} twapi_etw} |
||||
package ifneeded twapi_eventlog 4.7.2 \ |
||||
{load {} twapi_eventlog} |
||||
package ifneeded twapi_mstask 4.7.2 \ |
||||
{load {} twapi_mstask} |
||||
package ifneeded twapi_multimedia 4.7.2 \ |
||||
{load {} twapi_multimedia} |
||||
package ifneeded twapi_namedpipe 4.7.2 \ |
||||
{load {} twapi_namedpipe} |
||||
package ifneeded twapi_network 4.7.2 \ |
||||
{load {} twapi_network} |
||||
package ifneeded twapi_nls 4.7.2 \ |
||||
{load {} twapi_nls} |
||||
package ifneeded twapi_os 4.7.2 \ |
||||
{load {} twapi_os} |
||||
package ifneeded twapi_pdh 4.7.2 \ |
||||
{load {} twapi_pdh} |
||||
package ifneeded twapi_process 4.7.2 \ |
||||
{load {} twapi_process} |
||||
package ifneeded twapi_rds 4.7.2 \ |
||||
{load {} twapi_rds} |
||||
package ifneeded twapi_resource 4.7.2 \ |
||||
{load {} twapi_resource} |
||||
package ifneeded twapi_service 4.7.2 \ |
||||
{load {} twapi_service} |
||||
package ifneeded twapi_share 4.7.2 \ |
||||
{load {} twapi_share} |
||||
package ifneeded twapi_shell 4.7.2 \ |
||||
{load {} twapi_shell} |
||||
package ifneeded twapi_storage 4.7.2 \ |
||||
{load {} twapi_storage} |
||||
package ifneeded twapi_ui 4.7.2 \ |
||||
{load {} twapi_ui} |
||||
package ifneeded twapi_input 4.7.2 \ |
||||
{load {} twapi_input} |
||||
package ifneeded twapi_winsta 4.7.2 \ |
||||
{load {} twapi_winsta} |
||||
package ifneeded twapi_wmi 4.7.2 \ |
||||
{load {} twapi_wmi} |
||||
|
||||
package ifneeded twapi 4.7.2 [subst { |
||||
twapi::set_scriptdir [list $dir] |
||||
package require twapi_base 4.7.2 |
||||
source [list [file join $dir twapi_entry.tcl]] |
||||
package require metoo 4.7.2 |
||||
package require twapi_com 4.7.2 |
||||
package require twapi_msi 4.7.2 |
||||
package require twapi_power 4.7.2 |
||||
package require twapi_printer 4.7.2 |
||||
package require twapi_synch 4.7.2 |
||||
package require twapi_security 4.7.2 |
||||
package require twapi_account 4.7.2 |
||||
package require twapi_apputil 4.7.2 |
||||
package require twapi_clipboard 4.7.2 |
||||
package require twapi_console 4.7.2 |
||||
package require twapi_crypto 4.7.2 |
||||
package require twapi_device 4.7.2 |
||||
package require twapi_etw 4.7.2 |
||||
package require twapi_eventlog 4.7.2 |
||||
package require twapi_mstask 4.7.2 |
||||
package require twapi_multimedia 4.7.2 |
||||
package require twapi_namedpipe 4.7.2 |
||||
package require twapi_network 4.7.2 |
||||
package require twapi_nls 4.7.2 |
||||
package require twapi_os 4.7.2 |
||||
package require twapi_pdh 4.7.2 |
||||
package require twapi_process 4.7.2 |
||||
package require twapi_rds 4.7.2 |
||||
package require twapi_resource 4.7.2 |
||||
package require twapi_service 4.7.2 |
||||
package require twapi_share 4.7.2 |
||||
package require twapi_shell 4.7.2 |
||||
package require twapi_storage 4.7.2 |
||||
package require twapi_ui 4.7.2 |
||||
package require twapi_input 4.7.2 |
||||
package require twapi_winsta 4.7.2 |
||||
package require twapi_wmi 4.7.2 |
||||
package provide twapi 4.7.2 |
||||
}] |
@ -0,0 +1,136 @@
|
||||
# |
||||
# Copyright (c) 2003-2012 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi { |
||||
variable _power_monitors |
||||
set _power_monitors [dict create] |
||||
} |
||||
|
||||
# Get the power status of the system |
||||
proc twapi::get_power_status {} { |
||||
lassign [GetSystemPowerStatus] ac battery lifepercent reserved lifetime fulllifetime |
||||
|
||||
set acstatus unknown |
||||
if {$ac == 0} { |
||||
set acstatus off |
||||
} elseif {$ac == 1} { |
||||
# Note only value 1 is "on", not just any non-0 value |
||||
set acstatus on |
||||
} |
||||
|
||||
set batterycharging unknown |
||||
if {$battery == -1} { |
||||
set batterystate unknown |
||||
} elseif {$battery & 128} { |
||||
set batterystate notpresent; # No battery |
||||
} else { |
||||
if {$battery & 8} { |
||||
set batterycharging true |
||||
} else { |
||||
set batterycharging false |
||||
} |
||||
if {$battery & 4} { |
||||
set batterystate critical |
||||
} elseif {$battery & 2} { |
||||
set batterystate low |
||||
} else { |
||||
set batterystate high |
||||
} |
||||
} |
||||
|
||||
set batterylifepercent unknown |
||||
if {$lifepercent >= 0 && $lifepercent <= 100} { |
||||
set batterylifepercent $lifepercent |
||||
} |
||||
|
||||
set batterylifetime $lifetime |
||||
if {$lifetime == -1} { |
||||
set batterylifetime unknown |
||||
} |
||||
|
||||
set batteryfulllifetime $fulllifetime |
||||
if {$fulllifetime == -1} { |
||||
set batteryfulllifetime unknown |
||||
} |
||||
|
||||
return [kl_create2 { |
||||
-acstatus |
||||
-batterystate |
||||
-batterycharging |
||||
-batterylifepercent |
||||
-batterylifetime |
||||
-batteryfulllifetime |
||||
} [list $acstatus $batterystate $batterycharging $batterylifepercent $batterylifetime $batteryfulllifetime]] |
||||
} |
||||
|
||||
|
||||
# Power notification callback |
||||
proc twapi::_power_handler {msg power_event lparam msgpos ticks} { |
||||
variable _power_monitors |
||||
|
||||
if {[dict size $_power_monitors] == 0} { |
||||
return; # Not an error, could have deleted while already queued |
||||
} |
||||
|
||||
if {![kl_vget { |
||||
0 apmquerysuspend |
||||
2 apmquerysuspendfailed |
||||
4 apmsuspend |
||||
6 apmresumecritical |
||||
7 apmresumesuspend |
||||
9 apmbatterylow |
||||
10 apmpowerstatuschange |
||||
11 apmoemevent |
||||
18 apmresumeautomatic |
||||
} $power_event power_event]} { |
||||
return; # Do not support this event |
||||
} |
||||
|
||||
dict for {id script} $_power_monitors { |
||||
set code [catch {uplevel #0 [linsert $script end $power_event $lparam]} msg] |
||||
if {$code == 1} { |
||||
# Error - put in background but we do not abort |
||||
after 0 [list error $msg $::errorInfo $::errorCode] |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
proc twapi::start_power_monitor {script} { |
||||
variable _power_monitors |
||||
|
||||
set script [lrange $script 0 end]; # Verify syntactically a list |
||||
|
||||
set id "power#[TwapiId]" |
||||
if {[dict size $_power_monitors] == 0} { |
||||
# No power monitoring in progress. Start it |
||||
# 0x218 -> WM_POWERBROADCAST |
||||
_register_script_wm_handler 0x218 [list [namespace current]::_power_handler] 1 |
||||
} |
||||
|
||||
dict set _power_monitors $id $script |
||||
return $id |
||||
} |
||||
|
||||
|
||||
# Stop monitoring of the power |
||||
proc twapi::stop_power_monitor {id} { |
||||
variable _power_monitors |
||||
|
||||
if {![dict exists $_power_monitors $id]} { |
||||
return |
||||
} |
||||
|
||||
dict unset _power_monitors $id |
||||
if {[dict size $_power_monitors] == 0} { |
||||
_unregister_script_wm_handler 0x218 [list [namespace current]::_power_handler] |
||||
} |
||||
} |
||||
|
||||
# Hack to work with the various build configuration. |
||||
if {[info commands ::twapi::get_version] ne ""} { |
||||
package provide twapi_power [::twapi::get_version -patchlevel] |
||||
} |
@ -0,0 +1,58 @@
|
||||
# |
||||
# Copyright (c) 2004-2006 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi {} |
||||
|
||||
proc twapi::enumerate_printers {args} { |
||||
array set opts [parseargs args { |
||||
{proximity.arg all {local remote all any}} |
||||
} -maxleftover 0] |
||||
|
||||
set result [list ] |
||||
foreach elem [Twapi_EnumPrinters_Level4 \ |
||||
[string map {all 6 any 6 local 2 remote 4} $opts(proximity)] \ |
||||
] { |
||||
lappend result [list [lindex $elem 0] [lindex $elem 1] \ |
||||
[_symbolize_printer_attributes [lindex $elem 2]]] |
||||
} |
||||
return [list {-name -server -attrs} $result] |
||||
} |
||||
|
||||
|
||||
# Utilities |
||||
# |
||||
proc twapi::_symbolize_printer_attributes {attr} { |
||||
return [_make_symbolic_bitmask $attr { |
||||
queued 0x00000001 |
||||
direct 0x00000002 |
||||
default 0x00000004 |
||||
shared 0x00000008 |
||||
network 0x00000010 |
||||
hidden 0x00000020 |
||||
local 0x00000040 |
||||
enabledevq 0x00000080 |
||||
keepprintedjobs 0x00000100 |
||||
docompletefirst 0x00000200 |
||||
workoffline 0x00000400 |
||||
enablebidi 0x00000800 |
||||
rawonly 0x00001000 |
||||
published 0x00002000 |
||||
fax 0x00004000 |
||||
ts 0x00008000 |
||||
pusheduser 0x00020000 |
||||
pushedmachine 0x00040000 |
||||
machine 0x00080000 |
||||
friendlyname 0x00100000 |
||||
tsgenericdriver 0x00200000 |
||||
peruser 0x00400000 |
||||
enterprisecloud 0x00800000 |
||||
}] |
||||
} |
||||
|
||||
# Hack to work with the various build configuration. |
||||
if {[info commands ::twapi::get_version] ne ""} { |
||||
package provide twapi_printer [::twapi::get_version -patchlevel] |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,191 @@
|
||||
# |
||||
# Copyright (c) 2010, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Remote Desktop Services - TBD - document and test |
||||
|
||||
namespace eval twapi {} |
||||
|
||||
proc twapi::rds_enumerate_sessions {args} { |
||||
array set opts [parseargs args { |
||||
{hserver.arg 0} |
||||
state.arg |
||||
} -maxleftover 0] |
||||
|
||||
set states {active connected connectquery shadow disconnected idle listen reset down init} |
||||
if {[info exists opts(state)]} { |
||||
if {[string is integer -strict $opts(state)]} { |
||||
set state $opts(state) |
||||
} else { |
||||
set state [lsearch -exact $states $opts(state)] |
||||
if {$state < 0} { |
||||
error "Invalid value '$opts(state)' specified for -state option." |
||||
} |
||||
} |
||||
} |
||||
|
||||
set sessions [WTSEnumerateSessions $opts(hserver)] |
||||
|
||||
if {[info exists state]} { |
||||
set sessions [recordarray get $sessions -filter [list [list State == $state]]] |
||||
} |
||||
|
||||
set result {} |
||||
foreach {sess rec} [recordarray getdict $sessions -key SessionId -format dict] { |
||||
set state [lindex $states [kl_get $rec State]] |
||||
if {$state eq ""} { |
||||
set state [kl_get $rec State] |
||||
} |
||||
lappend result $sess [list -tssession [kl_get $rec SessionId] \ |
||||
-winstaname [kl_get $rec pWinStationName] \ |
||||
-state $state] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
proc twapi::rds_disconnect_session args { |
||||
array set opts [parseargs args { |
||||
{hserver.arg 0} |
||||
{tssession.int -1} |
||||
{async.bool false} |
||||
} -maxleftover 0] |
||||
|
||||
WTSDisconnectSession $opts(hserver) $opts(tssession) [expr {! $opts(async)}] |
||||
|
||||
} |
||||
|
||||
proc twapi::rds_logoff_session args { |
||||
array set opts [parseargs args { |
||||
{hserver.arg 0} |
||||
{tssession.int -1} |
||||
{async.bool false} |
||||
} -maxleftover 0] |
||||
|
||||
WTSLogoffSession $opts(hserver) $opts(tssession) [expr {! $opts(async)}] |
||||
} |
||||
|
||||
proc twapi::rds_query_session_information {infoclass args} { |
||||
array set opts [parseargs args { |
||||
{hserver.arg 0} |
||||
{tssession.int -1} |
||||
} -maxleftover 0] |
||||
|
||||
return [WTSQuerySessionInformation $opts(hserver) $opts(tssession) $infoclass] |
||||
} |
||||
|
||||
interp alias {} twapi::rds_get_session_appname {} twapi::rds_query_session_information 1 |
||||
interp alias {} twapi::rds_get_session_clientdir {} twapi::rds_query_session_information 11 |
||||
interp alias {} twapi::rds_get_session_clientname {} twapi::rds_query_session_information 10 |
||||
interp alias {} twapi::rds_get_session_userdomain {} twapi::rds_query_session_information 7 |
||||
interp alias {} twapi::rds_get_session_initialprogram {} twapi::rds_query_session_information 0 |
||||
interp alias {} twapi::rds_get_session_oemid {} twapi::rds_query_session_information 3 |
||||
interp alias {} twapi::rds_get_session_user {} twapi::rds_query_session_information 5 |
||||
interp alias {} twapi::rds_get_session_winsta {} twapi::rds_query_session_information 6 |
||||
interp alias {} twapi::rds_get_session_intialdir {} twapi::rds_query_session_information 2 |
||||
interp alias {} twapi::rds_get_session_clientbuild {} twapi::rds_query_session_information 9 |
||||
interp alias {} twapi::rds_get_session_clienthwid {} twapi::rds_query_session_information 13 |
||||
interp alias {} twapi::rds_get_session_state {} twapi::rds_query_session_information 8 |
||||
interp alias {} twapi::rds_get_session_id {} twapi::rds_query_session_information 4 |
||||
interp alias {} twapi::rds_get_session_productid {} twapi::rds_query_session_information 12 |
||||
interp alias {} twapi::rds_get_session_protocol {} twapi::rds_query_session_information 16 |
||||
|
||||
|
||||
proc twapi::rds_send_message {args} { |
||||
|
||||
array set opts [parseargs args { |
||||
{hserver.arg 0} |
||||
tssession.int |
||||
title.arg |
||||
message.arg |
||||
{buttons.arg ok} |
||||
{icon.arg information} |
||||
defaultbutton.arg |
||||
{modality.arg task {task appl application system}} |
||||
{justify.arg left {left right}} |
||||
rtl.bool |
||||
foreground.bool |
||||
topmost.bool |
||||
showhelp.bool |
||||
service.bool |
||||
timeout.int |
||||
async.bool |
||||
} -maxleftover 0 -nulldefault] |
||||
|
||||
if {![kl_vget { |
||||
ok {0 {ok}} |
||||
okcancel {1 {ok cancel}} |
||||
abortretryignore {2 {abort retry ignore}} |
||||
yesnocancel {3 {yes no cancel}} |
||||
yesno {4 {yes no}} |
||||
retrycancel {5 {retry cancel}} |
||||
canceltrycontinue {6 {cancel try continue}} |
||||
} $opts(buttons) buttons]} { |
||||
error "Invalid value '$opts(buttons)' specified for option -buttons." |
||||
} |
||||
|
||||
set style [lindex $buttons 0] |
||||
switch -exact -- $opts(icon) { |
||||
warning - |
||||
exclamation {setbits style 0x30} |
||||
asterisk - |
||||
information {setbits style 0x40} |
||||
question {setbits style 0x20} |
||||
error - |
||||
hand - |
||||
stop {setbits style 0x10} |
||||
default { |
||||
error "Invalid value '$opts(icon)' specified for option -icon." |
||||
} |
||||
} |
||||
|
||||
# Map the default button |
||||
switch -exact -- [lsearch -exact [lindex $buttons 1] $opts(defaultbutton)] { |
||||
1 {setbits style 0x100 } |
||||
2 {setbits style 0x200 } |
||||
3 {setbits style 0x300 } |
||||
default { |
||||
# First button, |
||||
# setbits style 0x000 |
||||
} |
||||
} |
||||
|
||||
switch -exact -- $opts(modality) { |
||||
system { setbits style 0x1000 } |
||||
task { setbits style 0x2000 } |
||||
appl - |
||||
application - |
||||
default { |
||||
# setbits style 0x0000 |
||||
} |
||||
} |
||||
|
||||
if {$opts(showhelp)} { setbits style 0x00004000 } |
||||
if {$opts(rtl)} { setbits style 0x00100000 } |
||||
if {$opts(justify) eq "right"} { setbits style 0x00080000 } |
||||
if {$opts(topmost)} { setbits style 0x00040000 } |
||||
if {$opts(foreground)} { setbits style 0x00010000 } |
||||
if {$opts(service)} { setbits style 0x00200000 } |
||||
|
||||
set response [WTSSendMessage $opts(hserver) $opts(tssession) $opts(title) \ |
||||
$opts(message) $style $opts(timeout) \ |
||||
[expr {!$opts(async)}]] |
||||
|
||||
switch -exact -- $response { |
||||
1 { return ok } |
||||
2 { return cancel } |
||||
3 { return abort } |
||||
4 { return retry } |
||||
5 { return ignore } |
||||
6 { return yes } |
||||
7 { return no } |
||||
8 { return close } |
||||
9 { return help } |
||||
10 { return tryagain } |
||||
11 { return continue } |
||||
32000 { return timeout } |
||||
32001 { return async } |
||||
default { return $response } |
||||
} |
||||
} |
@ -0,0 +1,490 @@
|
||||
# |
||||
# Copyright (c) 2020 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi {} |
||||
|
||||
# |
||||
# TBD -32bit and -64bit options are not documented |
||||
# pending test cases |
||||
|
||||
proc twapi::reg_key_copy {hkey to_hkey args} { |
||||
parseargs args { |
||||
subkey.arg |
||||
copysecd.bool |
||||
} -setvars -maxleftover 0 -nulldefault |
||||
|
||||
if {$copysecd} { |
||||
RegCopyTree $hkey $subkey $to_hkey |
||||
} else { |
||||
SHCopyKey $hkey $subkey $to_hkey |
||||
} |
||||
} |
||||
|
||||
proc twapi::reg_key_create {hkey subkey args} { |
||||
# TBD - document -link |
||||
# [opt_def [cmd -link] [arg BOOL]] If [const true], [arg SUBKEY] is stored as the |
||||
# value of the [const SymbolicLinkValue] value under [arg HKEY]. Default is |
||||
# [const false]. |
||||
parseargs args { |
||||
{access.arg generic_read} |
||||
{inherit.bool 0} |
||||
{secd.arg ""} |
||||
{volatile.bool 0 0x1} |
||||
{link.bool 0 0x2} |
||||
{backup.bool 0 0x4} |
||||
32bit |
||||
64bit |
||||
disposition.arg |
||||
} -maxleftover 0 -setvars |
||||
|
||||
set access [_access_rights_to_mask $access] |
||||
# Note: Following might be set via -access as well. The -32bit and -64bit |
||||
# options just make it a little more convenient for caller |
||||
if {$32bit} { |
||||
set access [expr {$access | 0x200}] |
||||
} |
||||
if {$64bit} { |
||||
set access [expr {$access | 0x100}] |
||||
} |
||||
lassign [RegCreateKeyEx \ |
||||
$hkey \ |
||||
$subkey \ |
||||
0 \ |
||||
"" \ |
||||
[expr {$volatile | $backup}] \ |
||||
$access \ |
||||
[_make_secattr $secd $inherit] \ |
||||
] hkey disposition_value |
||||
if {[info exists disposition]} { |
||||
upvar 1 $disposition created_or_existed |
||||
if {$disposition_value == 1} { |
||||
set created_or_existed created |
||||
} else { |
||||
# disposition_value == 2 |
||||
set created_or_existed existed |
||||
} |
||||
} |
||||
return $hkey |
||||
} |
||||
|
||||
proc twapi::reg_key_delete {hkey subkey args} { |
||||
parseargs args { |
||||
32bit |
||||
64bit |
||||
} -maxleftover 0 -setvars |
||||
|
||||
# TBD - document options after adding tests |
||||
set access 0 |
||||
if {$32bit} { |
||||
set access [expr {$access | 0x200}] |
||||
} |
||||
if {$64bit} { |
||||
set access [expr {$access | 0x100}] |
||||
} |
||||
|
||||
RegDeleteKeyEx $hkey $subkey $access |
||||
} |
||||
|
||||
proc twapi::reg_keys {hkey {subkey {}}} { |
||||
if {$subkey ne ""} { |
||||
set hkey [reg_key_open $hkey $subkey] |
||||
} |
||||
try { |
||||
return [RegEnumKeyEx $hkey 0] |
||||
} finally { |
||||
if {$subkey ne ""} { |
||||
reg_key_close $hkey |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc twapi::reg_key_open {hkey subkey args} { |
||||
# Not documented: -link, -32bit, -64bit |
||||
# [opt_def [cmd -link] [arg BOOL]] If [const true], specifies the key is a |
||||
# symbolic link. Defaults to [const false]. |
||||
parseargs args { |
||||
{link.bool 0} |
||||
{access.arg generic_read} |
||||
32bit |
||||
64bit |
||||
} -maxleftover 0 -setvars |
||||
|
||||
set access [_access_rights_to_mask $access] |
||||
# Note: Following might be set via -access as well. The -32bit and -64bit |
||||
# options just make it a little more convenient for caller |
||||
if {$32bit} { |
||||
set access [expr {$access | 0x200}] |
||||
} |
||||
if {$64bit} { |
||||
set access [expr {$access | 0x100}] |
||||
} |
||||
return [RegOpenKeyEx $hkey $subkey $link $access] |
||||
} |
||||
|
||||
proc twapi::reg_value_delete {hkey args} { |
||||
if {[llength $args] == 1} { |
||||
RegDeleteValue $hkey [lindex $args 0] |
||||
} elseif {[llength $args] == 2} { |
||||
RegDeleteKeyValue $hkey {*}$args |
||||
} else { |
||||
error "Wrong # args: should be \"reg_value_delete ?SUBKEY? VALUENAME\"" |
||||
} |
||||
} |
||||
|
||||
proc twapi::reg_key_current_user {args} { |
||||
parseargs args { |
||||
{access.arg generic_read} |
||||
32bit |
||||
64bit |
||||
} -maxleftover 0 -setvars |
||||
|
||||
set access [_access_rights_to_mask $access] |
||||
# Note: Following might be set via -access as well. The -32bit and -64bit |
||||
# options just make it a little more convenient for caller |
||||
if {$32bit} { |
||||
set access [expr {$access | 0x200}] |
||||
} |
||||
if {$64bit} { |
||||
set access [expr {$access | 0x100}] |
||||
} |
||||
return [RegOpenCurrentUser $access] |
||||
} |
||||
|
||||
proc twapi::reg_key_user_classes_root {usertoken args} { |
||||
parseargs args { |
||||
{access.arg generic_read} |
||||
32bit |
||||
64bit |
||||
} -maxleftover 0 -setvars |
||||
|
||||
set access [_access_rights_to_mask $access] |
||||
# Note: Following might be set via -access as well. The -32bit and -64bit |
||||
# options just make it a little more convenient for caller |
||||
if {$32bit} { |
||||
set access [expr {$access | 0x200}] |
||||
} |
||||
if {$64bit} { |
||||
set access [expr {$access | 0x100}] |
||||
} |
||||
return [RegOpenUserClassesRoot $usertoken 0 $access] |
||||
} |
||||
|
||||
proc twapi::reg_key_export {hkey filepath args} { |
||||
parseargs args { |
||||
{secd.arg {}} |
||||
{format.arg xp {win2k xp}} |
||||
{compress.bool 1} |
||||
} -setvars |
||||
|
||||
set format [dict get {win2k 1 xp 2} $format] |
||||
if {! $compress} { |
||||
set format [expr {$format | 4}] |
||||
} |
||||
twapi::eval_with_privileges { |
||||
RegSaveKeyEx $hkey $filepath [_make_secattr $secd 0] $format |
||||
} SeBackupPrivilege |
||||
} |
||||
|
||||
proc twapi::reg_key_import {hkey filepath args} { |
||||
parseargs args { |
||||
{volatile.bool 0 0x1} |
||||
{force.bool 0 0x8} |
||||
} -setvars |
||||
twapi::eval_with_privileges { |
||||
RegRestoreKey $hkey $filepath [expr {$force | $volatile}] |
||||
} {SeBackupPrivilege SeRestorePrivilege} |
||||
} |
||||
|
||||
proc twapi::reg_key_load {hkey hivename filepath} { |
||||
twapi::eval_with_privileges { |
||||
RegLoadKey $hkey $subkey $filepath |
||||
} {SeBackupPrivilege SeRestorePrivilege} |
||||
} |
||||
|
||||
proc twapi::reg_key_unload {hkey hivename} { |
||||
twapi::eval_with_privileges { |
||||
RegUnLoadKey $hkey $subkey |
||||
} {SeBackupPrivilege SeRestorePrivilege} |
||||
} |
||||
|
||||
proc twapi::reg_key_monitor {hkey hevent args} { |
||||
parseargs args { |
||||
{keys.bool 0 0x1} |
||||
{attr.bool 0 0x2} |
||||
{values.bool 0 0x4} |
||||
{secd.bool 0 0x8} |
||||
{subtree.bool 0} |
||||
} -setvars |
||||
|
||||
set filter [expr {$keys | $attr | $values | $secd}] |
||||
if {$filter == 0} { |
||||
set filter 0xf |
||||
} |
||||
|
||||
RegNotifyChangeKeyValue $hkey $subtree $filter $hevent 1 |
||||
} |
||||
|
||||
proc twapi::reg_value_names {hkey {subkey {}}} { |
||||
if {$subkey eq ""} { |
||||
# 0 - value names only |
||||
return [RegEnumValue $hkey 0] |
||||
} |
||||
set hkey [reg_key_open $hkey $subkey] |
||||
try { |
||||
# 0 - value names only |
||||
return [RegEnumValue $hkey 0] |
||||
} finally { |
||||
reg_key_close $hkey |
||||
} |
||||
} |
||||
|
||||
proc twapi::reg_values {hkey {subkey {}}} { |
||||
if {$subkey eq ""} { |
||||
# 3 -> 0x1 - return data values, 0x2 - cooked data |
||||
return [RegEnumValue $hkey 3] |
||||
} |
||||
set hkey [reg_key_open $hkey $subkey] |
||||
try { |
||||
# 3 -> 0x1 - return data values, 0x2 - cooked data |
||||
return [RegEnumValue $hkey 3] |
||||
} finally { |
||||
reg_key_close $hkey |
||||
} |
||||
} |
||||
|
||||
proc twapi::reg_values_raw {hkey {subkey {}}} { |
||||
if {$subkey eq ""} { |
||||
# 0x1 - return data values |
||||
return [RegEnumValue $hkey 1] |
||||
} |
||||
set hkey [reg_key_open $hkey $subkey] |
||||
try { |
||||
return [RegEnumValue $hkey 1] |
||||
} finally { |
||||
reg_key_close $hkey |
||||
} |
||||
} |
||||
|
||||
proc twapi::reg_value_raw {hkey args} { |
||||
if {[llength $args] == 1} { |
||||
return [RegQueryValueEx $hkey [lindex $args 0] false] |
||||
} elseif {[llength $args] == 2} { |
||||
return [RegGetValue $hkey {*}$args 0x1000ffff false] |
||||
} else { |
||||
error "wrong # args: should be \"reg_value_get HKEY ?SUBKEY? VALUENAME\"" |
||||
} |
||||
} |
||||
|
||||
proc twapi::reg_value {hkey args} { |
||||
if {[llength $args] == 1} { |
||||
return [RegQueryValueEx $hkey [lindex $args 0] true] |
||||
} elseif {[llength $args] == 2} { |
||||
return [RegGetValue $hkey {*}$args 0x1000ffff true] |
||||
} else { |
||||
error "wrong # args: should be \"reg_value_get HKEY ?SUBKEY? VALUENAME\"" |
||||
} |
||||
} |
||||
|
||||
if {[twapi::min_os_version 6]} { |
||||
proc twapi::reg_value_set {hkey args} { |
||||
if {[llength $args] == 3} { |
||||
return [RegSetValueEx $hkey {*}$args] |
||||
} elseif {[llength $args] == 4} { |
||||
return [RegSetKeyValue $hkey {*}$args] |
||||
} else { |
||||
error "wrong # args: should be \"reg_value_set HKEY ?SUBKEY? VALUENAME TYPE VALUE\"" |
||||
} |
||||
} |
||||
} else { |
||||
proc twapi::reg_value_set {hkey args} { |
||||
if {[llength $args] == 3} { |
||||
lassign $args value_name value_type value |
||||
} elseif {[llength $args] == 4} { |
||||
lassign $args subkey value_name value_type value |
||||
set hkey [reg_key_open $hkey $subkey -access key_set_value] |
||||
} else { |
||||
error "wrong # args: should be \"reg_value_set HKEY ?SUBKEY? VALUENAME TYPE VALUE\"" |
||||
} |
||||
try { |
||||
RegSetValueEx $hkey $value_name $value_type $value |
||||
} finally { |
||||
if {[info exists subkey]} { |
||||
# We opened hkey |
||||
reg_close_key $hkey |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc twapi::reg_key_override_undo {hkey} { |
||||
RegOverridePredefKey $hkey 0 |
||||
} |
||||
|
||||
proc twapi::_reg_walker {hkey path callback cbdata} { |
||||
# Callback for the key |
||||
set code [catch { |
||||
{*}$callback $cbdata $hkey $path |
||||
} cbdata ropts] |
||||
if {$code != 0} { |
||||
if {$code == 4} { |
||||
# Continue - skip children, continue with siblings |
||||
return $cbdata |
||||
} elseif {$code == 3} { |
||||
# Skip siblings as well |
||||
return -code break $cbdata |
||||
} elseif {$code == 2} { |
||||
# Stop complete iteration |
||||
return -code return $cbdata |
||||
} else { |
||||
return -options $ropts $cbdata |
||||
} |
||||
} |
||||
|
||||
# Iterate over child keys |
||||
foreach child_key [reg_keys $hkey] { |
||||
set child_hkey [reg_key_open $hkey $child_key] |
||||
try { |
||||
# Recurse to call into children |
||||
set code [catch { |
||||
_reg_walker $child_hkey [linsert $path end $child_key] $callback $cbdata |
||||
} cbdata ropts] |
||||
if {$code != 0 && $code != 4} { |
||||
if {$code == 3} { |
||||
# break - skip remaining child keys |
||||
return $cbdata |
||||
} elseif {$code == 2} { |
||||
# return - stop all iteration all up the tree |
||||
return -code return $cbdata |
||||
} else { |
||||
return -options $ropts $cbdata |
||||
} |
||||
} |
||||
} finally { |
||||
reg_key_close $child_hkey |
||||
} |
||||
} |
||||
|
||||
return $cbdata |
||||
} |
||||
|
||||
proc twapi::reg_walk {hkey args} { |
||||
parseargs args { |
||||
{subkey.arg {}} |
||||
callback.arg |
||||
{cbdata.arg ""} |
||||
} -maxleftover 0 -setvars |
||||
|
||||
|
||||
if {$subkey ne ""} { |
||||
set hkey [reg_key_open $hkey $subkey] |
||||
set path [list $subkey] |
||||
} else { |
||||
set path [list ] |
||||
} |
||||
|
||||
if {![info exists callback]} { |
||||
set callback [lambda {cbdata hkey path} {puts [join $path \\]}] |
||||
} |
||||
try { |
||||
set code [catch {_reg_walker $hkey $path $callback $cbdata } result ropts] |
||||
# Codes 2 (return), 3 (break) and 4 (continue) are just early terminations |
||||
if {$code == 1} { |
||||
return -options $ropts $result |
||||
} |
||||
} finally { |
||||
if {$subkey ne ""} { |
||||
reg_key_close $hkey |
||||
} |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
proc twapi::_reg_iterator_callback {cbdata hkey path args} { |
||||
set cmd [yield [list $hkey $path {*}$args]] |
||||
# Loop until valid argument |
||||
while {1} { |
||||
switch -exact -- $cmd { |
||||
"" - |
||||
next { return $cbdata } |
||||
stop { return -code return $cbdata } |
||||
parentsibling { return -code break $cbdata } |
||||
sibling { return -code continue $cbdata } |
||||
default { |
||||
set ret [yieldto return -level 0 -code error "Invalid argument \"$cmd\"."] |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc twapi::_reg_iterator_coro {hkey subkey} { |
||||
set cmd [yield [info coroutine]] |
||||
switch -exact -- $cmd { |
||||
"" - |
||||
next { |
||||
# Drop into reg_walk |
||||
} |
||||
stop - |
||||
parentsibling - |
||||
sibling { |
||||
return {} |
||||
} |
||||
default { |
||||
error "Invalid argument \"$cmd\"." |
||||
} |
||||
} |
||||
if {$subkey ne ""} { |
||||
set hkey [reg_key_open $hkey $subkey] |
||||
} |
||||
try { |
||||
reg_walk $hkey -callback [namespace current]::_reg_iterator_callback |
||||
} finally { |
||||
if {$subkey ne ""} { |
||||
reg_key_close $hkey |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
proc twapi::reg_iterator {hkey {subkey {}}} { |
||||
variable reg_walk_counter |
||||
|
||||
return [coroutine "regwalk#[incr reg_walk_counter]" _reg_iterator_coro $hkey $subkey] |
||||
} |
||||
|
||||
proc twapi::reg_tree {hkey {subkey {}}} { |
||||
|
||||
set iter [reg_iterator $hkey $subkey] |
||||
|
||||
set paths {} |
||||
while {[llength [set item [$iter next]]]} { |
||||
lappend paths [join [lindex $item 1] \\] |
||||
} |
||||
return $paths |
||||
} |
||||
|
||||
proc twapi::reg_tree_values {hkey {subkey {}}} { |
||||
|
||||
set iter [reg_iterator $hkey $subkey] |
||||
|
||||
set tree {} |
||||
# Note here we cannot ignore the first empty node corresponding |
||||
# to the root because we have to return any values it contains. |
||||
while {[llength [set item [$iter next]]]} { |
||||
dict set tree [join [lindex $item 1] \\] [reg_values [lindex $item 0]] |
||||
} |
||||
return $tree |
||||
} |
||||
|
||||
proc twapi::reg_tree_values_raw {hkey {subkey {}}} { |
||||
set iter [reg_iterator $hkey $subkey] |
||||
|
||||
set tree {} |
||||
while {[llength [set item [$iter next]]]} { |
||||
dict set tree [join [lindex $item 1] \\] [reg_values_raw [lindex $item 0]] |
||||
} |
||||
return $tree |
||||
} |
||||
|
@ -0,0 +1,458 @@
|
||||
# Commands related to resource manipulation |
||||
# |
||||
# Copyright (c) 2003-2012 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
package require twapi_nls |
||||
|
||||
# Retrieve version info for a file |
||||
proc twapi::get_file_version_resource {path args} { |
||||
array set opts [parseargs args { |
||||
all |
||||
datetime |
||||
signature |
||||
structversion |
||||
fileversion |
||||
productversion |
||||
flags |
||||
fileos |
||||
filetype |
||||
foundlangid |
||||
foundcodepage |
||||
langid.arg |
||||
codepage.arg |
||||
}] |
||||
|
||||
|
||||
set ver [Twapi_GetFileVersionInfo $path] |
||||
|
||||
trap { |
||||
array set verinfo [Twapi_VerQueryValue_FIXEDFILEINFO $ver] |
||||
|
||||
set result [list ] |
||||
if {$opts(all) || $opts(signature)} { |
||||
lappend result -signature [format 0x%x $verinfo(dwSignature)] |
||||
} |
||||
|
||||
if {$opts(all) || $opts(structversion)} { |
||||
lappend result -structversion "[expr {0xffff & ($verinfo(dwStrucVersion) >> 16)}].[expr {0xffff & $verinfo(dwStrucVersion)}]" |
||||
} |
||||
|
||||
if {$opts(all) || $opts(fileversion)} { |
||||
lappend result -fileversion "[expr {0xffff & ($verinfo(dwFileVersionMS) >> 16)}].[expr {0xffff & $verinfo(dwFileVersionMS)}].[expr {0xffff & ($verinfo(dwFileVersionLS) >> 16)}].[expr {0xffff & $verinfo(dwFileVersionLS)}]" |
||||
} |
||||
|
||||
if {$opts(all) || $opts(productversion)} { |
||||
lappend result -productversion "[expr {0xffff & ($verinfo(dwProductVersionMS) >> 16)}].[expr {0xffff & $verinfo(dwProductVersionMS)}].[expr {0xffff & ($verinfo(dwProductVersionLS) >> 16)}].[expr {0xffff & $verinfo(dwProductVersionLS)}]" |
||||
} |
||||
|
||||
if {$opts(all) || $opts(flags)} { |
||||
set flags [expr {$verinfo(dwFileFlags) & $verinfo(dwFileFlagsMask)}] |
||||
lappend result -flags \ |
||||
[_make_symbolic_bitmask \ |
||||
[expr {$verinfo(dwFileFlags) & $verinfo(dwFileFlagsMask)}] \ |
||||
{ |
||||
debug 1 |
||||
prerelease 2 |
||||
patched 4 |
||||
privatebuild 8 |
||||
infoinferred 16 |
||||
specialbuild 32 |
||||
} \ |
||||
] |
||||
} |
||||
|
||||
if {$opts(all) || $opts(fileos)} { |
||||
switch -exact -- [format %08x $verinfo(dwFileOS)] { |
||||
00010000 {set os dos} |
||||
00020000 {set os os216} |
||||
00030000 {set os os232} |
||||
00040000 {set os nt} |
||||
00050000 {set os wince} |
||||
00000001 {set os windows16} |
||||
00000002 {set os pm16} |
||||
00000003 {set os pm32} |
||||
00000004 {set os windows32} |
||||
00010001 {set os dos_windows16} |
||||
00010004 {set os dos_windows32} |
||||
00020002 {set os os216_pm16} |
||||
00030003 {set os os232_pm32} |
||||
00040004 {set os nt_windows32} |
||||
default {set os $verinfo(dwFileOS)} |
||||
} |
||||
lappend result -fileos $os |
||||
} |
||||
|
||||
if {$opts(all) || $opts(filetype)} { |
||||
switch -exact -- [expr {0+$verinfo(dwFileType)}] { |
||||
1 {set type application} |
||||
2 {set type dll} |
||||
3 { |
||||
set type "driver." |
||||
switch -exact -- [expr {0+$verinfo(dwFileSubtype)}] { |
||||
1 {append type printer} |
||||
2 {append type keyboard} |
||||
3 {append type language} |
||||
4 {append type display} |
||||
5 {append type mouse} |
||||
6 {append type network} |
||||
7 {append type system} |
||||
8 {append type installable} |
||||
9 {append type sound} |
||||
10 {append type comm} |
||||
11 {append type inputmethod} |
||||
12 {append type versionedprinter} |
||||
default {append type $verinfo(dwFileSubtype)} |
||||
} |
||||
} |
||||
4 { |
||||
set type "font." |
||||
switch -exact -- [expr {0+$verinfo(dwFileSubtype)}] { |
||||
1 {append type raster} |
||||
2 {append type vector} |
||||
3 {append type truetype} |
||||
default {append type $verinfo(dwFileSubtype)} |
||||
} |
||||
} |
||||
5 { set type "vxd.$verinfo(dwFileSubtype)" } |
||||
7 {set type staticlib} |
||||
default { |
||||
set type "$verinfo(dwFileType).$verinfo(dwFileSubtype)" |
||||
} |
||||
} |
||||
lappend result -filetype $type |
||||
} |
||||
|
||||
if {$opts(all) || $opts(datetime)} { |
||||
lappend result -datetime [expr {($verinfo(dwFileDateMS) << 32) + $verinfo(dwFileDateLS)}] |
||||
} |
||||
|
||||
# Any remaining arguments are treated as string names |
||||
|
||||
if {[llength $args] || $opts(foundlangid) || $opts(foundcodepage) || $opts(all)} { |
||||
# Find list of langid's and codepages and do closest match |
||||
set langid [expr {[info exists opts(langid)] ? $opts(langid) : [get_user_ui_langid]}] |
||||
set primary_langid [extract_primary_langid $langid] |
||||
set sub_langid [extract_sublanguage_langid $langid] |
||||
set cp [expr {[info exists opts(codepage)] ? $opts(codepage) : 0}] |
||||
|
||||
# Find a match in the following order: |
||||
# 0 Exact match for both langid and codepage |
||||
# 1 Exact match for langid |
||||
# 2 Primary langid matches (sublang does not) and exact codepage |
||||
# 3 Primary langid matches (sublang does not) |
||||
# 4 Language neutral |
||||
# 5 English |
||||
# 6 First langcp in list or "00000000" |
||||
set match(7) "00000000"; # In case list is empty |
||||
foreach langcp [Twapi_VerQueryValue_TRANSLATIONS $ver] { |
||||
set verlangid 0x[string range $langcp 0 3] |
||||
set vercp 0x[string range $langcp 4 7] |
||||
if {$verlangid == $langid && $vercp == $cp} { |
||||
set match(0) $langcp |
||||
break; # No need to look further |
||||
} |
||||
if {[info exists match(1)]} continue |
||||
if {$verlangid == $langid} { |
||||
set match(1) $langcp |
||||
continue; # Continue to look for match(0) |
||||
} |
||||
if {[info exists match(2)]} continue |
||||
set verprimary [extract_primary_langid $verlangid] |
||||
if {$verprimary == $primary_langid && $vercp == $cp} { |
||||
set match(2) $langcp |
||||
continue; # Continue to look for match(1) or better |
||||
} |
||||
if {[info exists match(3)]} continue |
||||
if {$verprimary == $primary_langid} { |
||||
set match(3) $langcp |
||||
continue; # Continue to look for match(2) or better |
||||
} |
||||
if {[info exists match(4)]} continue |
||||
if {$verprimary == 0} { |
||||
set match(4) $langcp; # LANG_NEUTRAL |
||||
continue; # Continue to look for match(3) or better |
||||
} |
||||
if {[info exists match(5)]} continue |
||||
if {$verprimary == 9} { |
||||
set match(5) $langcp; # English |
||||
continue; # Continue to look for match(4) or better |
||||
} |
||||
if {![info exists match(6)]} { |
||||
set match(6) $langcp |
||||
} |
||||
} |
||||
|
||||
# Figure out what is the best match we have |
||||
for {set i 0} {$i <= 7} {incr i} { |
||||
if {[info exists match($i)]} { |
||||
break |
||||
} |
||||
} |
||||
|
||||
if {$opts(foundlangid) || $opts(all)} { |
||||
set langid 0x[string range $match($i) 0 3] |
||||
lappend result -foundlangid [list $langid [VerLanguageName $langid]] |
||||
} |
||||
|
||||
if {$opts(foundcodepage) || $opts(all)} { |
||||
lappend result -foundcodepage 0x[string range $match($i) 4 7] |
||||
} |
||||
|
||||
foreach sname $args { |
||||
lappend result $sname [Twapi_VerQueryValue_STRING $ver $match($i) $sname] |
||||
} |
||||
|
||||
} |
||||
|
||||
} finally { |
||||
Twapi_FreeFileVersionInfo $ver |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
proc twapi::begin_resource_update {path args} { |
||||
array set opts [parseargs args { |
||||
deleteall |
||||
} -maxleftover 0] |
||||
|
||||
return [BeginUpdateResource $path $opts(deleteall)] |
||||
} |
||||
|
||||
# Note this is not an alias because we want to control arguments |
||||
# to UpdateResource (which can take more args that specified here) |
||||
proc twapi::delete_resource {hmod restype resname langid} { |
||||
UpdateResource $hmod $restype $resname $langid |
||||
} |
||||
|
||||
|
||||
# Note this is not an alias because we want to make sure $bindata is specified |
||||
# as an argument else it will have the effect of deleting a resource |
||||
proc twapi::update_resource {hmod restype resname langid bindata} { |
||||
UpdateResource $hmod $restype $resname $langid $bindata |
||||
} |
||||
|
||||
proc twapi::end_resource_update {hmod args} { |
||||
array set opts [parseargs args { |
||||
discard |
||||
} -maxleftover 0] |
||||
|
||||
return [EndUpdateResource $hmod $opts(discard)] |
||||
} |
||||
|
||||
proc twapi::read_resource {hmod restype resname langid} { |
||||
return [Twapi_LoadResource $hmod [FindResourceEx $hmod $restype $resname $langid]] |
||||
} |
||||
|
||||
proc twapi::read_resource_string {hmod resname langid} { |
||||
# As an aside, note that we do not use a LoadString call |
||||
# because it does not allow for specification of a langid |
||||
|
||||
# For a reference to how strings are stored, see |
||||
# http://blogs.msdn.com/b/oldnewthing/archive/2004/01/30/65013.aspx |
||||
# or http://support.microsoft.com/kb/196774 |
||||
|
||||
if {![string is integer -strict $resname]} { |
||||
error "String resources must have an integer id" |
||||
} |
||||
|
||||
lassign [resource_stringid_to_stringblockid $resname] block_id index_within_block |
||||
|
||||
return [lindex \ |
||||
[resource_stringblock_to_strings \ |
||||
[read_resource $hmod 6 $block_id $langid] ] \ |
||||
$index_within_block] |
||||
} |
||||
|
||||
# Give a list of strings, formats it as a string block. Number of strings |
||||
# must not be greater than 16. If less than 16 strings, remaining are |
||||
# treated as empty. |
||||
proc twapi::strings_to_resource_stringblock {strings} { |
||||
if {[llength $strings] > 16} { |
||||
error "Cannot have more than 16 strings in a resource string block." |
||||
} |
||||
|
||||
for {set i 0} {$i < 16} {incr i} { |
||||
set s [lindex $strings $i] |
||||
set n [string length $s] |
||||
append bin [binary format sa* $n [encoding convertto unicode $s]] |
||||
} |
||||
|
||||
return $bin |
||||
} |
||||
|
||||
proc twapi::resource_stringid_to_stringblockid {id} { |
||||
# Strings are stored in blocks of 16, with block id's beginning at 1, not 0 |
||||
return [list [expr {($id / 16) + 1}] [expr {$id & 15}]] |
||||
} |
||||
|
||||
proc twapi::extract_resources {hmod {withdata 0}} { |
||||
set result [dict create] |
||||
foreach type [enumerate_resource_types $hmod] { |
||||
set typedict [dict create] |
||||
foreach name [enumerate_resource_names $hmod $type] { |
||||
set namedict [dict create] |
||||
foreach lang [enumerate_resource_languages $hmod $type $name] { |
||||
if {$withdata} { |
||||
dict set namedict $lang [read_resource $hmod $type $name $lang] |
||||
} else { |
||||
dict set namedict $lang {} |
||||
} |
||||
} |
||||
dict set typedict $name $namedict |
||||
} |
||||
dict set result $type $typedict |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# TBD - test |
||||
proc twapi::write_bmp_file {filename bmp} { |
||||
# Assumes $bmp is clipboard content in format 8 (CF_DIB) |
||||
|
||||
# First parse the bitmap data to collect header information |
||||
binary scan $bmp "iiissiiiiii" size width height planes bitcount compression sizeimage xpelspermeter ypelspermeter clrused clrimportant |
||||
|
||||
# We only handle BITMAPINFOHEADER right now (size must be 40) |
||||
if {$size != 40} { |
||||
error "Unsupported bitmap format. Header size=$size" |
||||
} |
||||
|
||||
# We need to figure out the offset to the actual bitmap data |
||||
# from the start of the file header. For this we need to know the |
||||
# size of the color table which directly follows the BITMAPINFOHEADER |
||||
if {$bitcount == 0} { |
||||
error "Unsupported format: implicit JPEG or PNG" |
||||
} elseif {$bitcount == 1} { |
||||
set color_table_size 2 |
||||
} elseif {$bitcount == 4} { |
||||
# TBD - Not sure if this is the size or the max size |
||||
set color_table_size 16 |
||||
} elseif {$bitcount == 8} { |
||||
# TBD - Not sure if this is the size or the max size |
||||
set color_table_size 256 |
||||
} elseif {$bitcount == 16 || $bitcount == 32} { |
||||
if {$compression == 0} { |
||||
# BI_RGB |
||||
set color_table_size $clrused |
||||
} elseif {$compression == 3} { |
||||
# BI_BITFIELDS |
||||
set color_table_size 3 |
||||
} else { |
||||
error "Unsupported compression type '$compression' for bitcount value $bitcount" |
||||
} |
||||
} elseif {$bitcount == 24} { |
||||
set color_table_size $clrused |
||||
} else { |
||||
error "Unsupported value '$bitcount' in bitmap bitcount field" |
||||
} |
||||
|
||||
set filehdr_size 14; # sizeof(BITMAPFILEHEADER) |
||||
set bitmap_file_offset [expr {$filehdr_size+$size+($color_table_size*4)}] |
||||
set filehdr [binary format "a2 i x2 x2 i" "BM" [expr {$filehdr_size + [string length $bmp]}] $bitmap_file_offset] |
||||
|
||||
set fd [open $filename w] |
||||
fconfigure $fd -translation binary |
||||
|
||||
puts -nonewline $fd $filehdr |
||||
puts -nonewline $fd $bmp |
||||
|
||||
close $fd |
||||
} |
||||
|
||||
proc twapi::_load_image {flags type hmod path args} { |
||||
# The flags arg is generally 0x10 (load from file), or 0 (module) |
||||
# or'ed with 0x8000 (shared). The latter can be overridden by |
||||
# the -shared option but should not be except when loading from module. |
||||
array set opts [parseargs args { |
||||
{createdibsection.bool 0 0x2000} |
||||
{defaultsize.bool 0 0x40} |
||||
height.int |
||||
{loadtransparent.bool 0 0x20} |
||||
{monochrome.bool 0 0x1} |
||||
{shared.bool 0 0x8000} |
||||
{vgacolor.bool 0 0x80} |
||||
width.int |
||||
} -maxleftover 0 -nulldefault] |
||||
|
||||
set flags [expr {$flags | $opts(defaultsize) | $opts(loadtransparent) | $opts(monochrome) | $opts(shared) | $opts(vgacolor)}] |
||||
|
||||
set h [LoadImage $hmod $path $type $opts(width) $opts(height) $flags] |
||||
# Cast as _SHARED if required to offer some protection against |
||||
# being freed using DestroyIcon etc. |
||||
set type [lindex {HGDIOBJ HICON HCURSOR} $type] |
||||
if {$flags & 0x8000} { |
||||
append type _SHARED |
||||
} |
||||
return [cast_handle $h $type] |
||||
} |
||||
|
||||
|
||||
proc twapi::_load_image_from_system {type id args} { |
||||
variable _oem_image_syms |
||||
|
||||
if {![string is integer -strict $id]} { |
||||
if {![info exists _oem_image_syms]} { |
||||
# Bitmap symbols (type 0) |
||||
dict set _oem_image_syms 0 { |
||||
CLOSE 32754 UPARROW 32753 |
||||
DNARROW 32752 RGARROW 32751 |
||||
LFARROW 32750 REDUCE 32749 |
||||
ZOOM 32748 RESTORE 32747 |
||||
REDUCED 32746 ZOOMD 32745 |
||||
RESTORED 32744 UPARROWD 32743 |
||||
DNARROWD 32742 RGARROWD 32741 |
||||
LFARROWD 32740 MNARROW 32739 |
||||
COMBO 32738 UPARROWI 32737 |
||||
DNARROWI 32736 RGARROWI 32735 |
||||
LFARROWI 32734 SIZE 32766 |
||||
BTSIZE 32761 CHECK 32760 |
||||
CHECKBOXES 32759 BTNCORNERS 32758 |
||||
} |
||||
# Icon symbols (type 1) |
||||
dict set _oem_image_syms 1 { |
||||
SAMPLE 32512 HAND 32513 |
||||
QUES 32514 BANG 32515 |
||||
NOTE 32516 WINLOGO 32517 |
||||
WARNING 32515 ERROR 32513 |
||||
INFORMATION 32516 SHIELD 32518 |
||||
} |
||||
# Cursor symbols (type 2) |
||||
dict set _oem_image_syms 2 { |
||||
NORMAL 32512 IBEAM 32513 |
||||
WAIT 32514 CROSS 32515 |
||||
UP 32516 SIZENWSE 32642 |
||||
SIZENESW 32643 SIZEWE 32644 |
||||
SIZENS 32645 SIZEALL 32646 |
||||
NO 32648 HAND 32649 |
||||
APPSTARTING 32650 |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
set id [dict get $_oem_image_syms $type [string toupper $id]] |
||||
# Built-in system images must always be loaded shared (0x8000) |
||||
return [_load_image 0x8000 $type NULL $id {*}$args] |
||||
} |
||||
|
||||
|
||||
# 0x10 -> LR_LOADFROMFILE. Also 0x8000 not set (meaning unshared) |
||||
interp alias {} twapi::load_bitmap_from_file {} twapi::_load_image 0x10 0 NULL |
||||
interp alias {} twapi::load_icon_from_file {} twapi::_load_image 0x10 1 NULL |
||||
interp alias {} twapi::load_cursor_from_file {} twapi::_load_image 0x10 2 NULL |
||||
|
||||
interp alias {} twapi::load_bitmap_from_module {} twapi::_load_image 0 0 |
||||
interp alias {} twapi::load_icon_from_module {} twapi::_load_image 0 1 |
||||
interp alias {} twapi::load_cursor_from_module {} twapi::_load_image 0 2 |
||||
|
||||
interp alias {} twapi::load_bitmap_from_system {} twapi::_load_image_from_system 0 |
||||
interp alias {} twapi::load_icon_from_system {} twapi::_load_image_from_system 1 |
||||
interp alias {} twapi::load_cursor_from_system {} twapi::_load_image_from_system 2 |
||||
|
||||
interp alias {} twapi::free_icon {} twapi::DestroyIcon |
||||
interp alias {} twapi::free_bitmap {} twapi::DeleteObject |
||||
interp alias {} twapi::free_cursor {} twapi::DestroyCursor |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,966 @@
|
||||
# |
||||
# Copyright (c) 2003-2014, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi { |
||||
# Win SDK based structure definitions |
||||
|
||||
record SHARE_INFO_0 {-name} |
||||
record SHARE_INFO_1 {-name -type -comment} |
||||
record SHARE_INFO_2 {-name -type -comment -permissions -max_conn -current_conn -path -passwd} |
||||
record SHARE_INFO_502 {-name -type -comment -permissions -max_conn -current_conn -path -passwd -reserved -secd} |
||||
|
||||
record USE_INFO_0 {-localdevice -remoteshare} |
||||
record USE_INFO_1 {-localdevice -remoteshare -password -status -type -opencount -usecount} |
||||
record USE_INFO_2 {-localdevice -remoteshare -password -status -type -opencount -usecount -user -domain} |
||||
|
||||
record SESSION_INFO_0 {-clientname} |
||||
record SESSION_INFO_1 {-clientname -user -opencount -activeseconds -idleseconds -attrs} |
||||
record SESSION_INFO_2 {-clientname -user -opencount -activeseconds -idleseconds -attrs -clienttype} |
||||
record SESSION_INFO_502 {-clientname -user -opencount -activeseconds -idleseconds -attrs -clienttype -transport} |
||||
record SESSION_INFO_10 {-clientname -user -activeseconds -idleseconds} |
||||
|
||||
record FILE_INFO_2 {-id} |
||||
record FILE_INFO_3 {-id -permissions -lockcount -path -user} |
||||
|
||||
record CONNECTION_INFO_0 {-id} |
||||
record CONNECTION_INFO_1 {-id -type -opencount -usercount -activeseconds -user -netname} |
||||
|
||||
struct NETRESOURCE { |
||||
DWORD dwScope; |
||||
DWORD dwType; |
||||
DWORD dwDisplayType; |
||||
DWORD dwUsage; |
||||
LPCWSTR lpLocalName; |
||||
LPCWSTR lpRemoteName; |
||||
LPCWSTR lpComment; |
||||
LPCWSTR lpProvider; |
||||
}; |
||||
|
||||
struct NETINFOSTRUCT { |
||||
DWORD cbStructure; |
||||
DWORD dwProviderVersion; |
||||
DWORD dwStatus; |
||||
DWORD dwCharacteristics; |
||||
HANDLE dwHandle; |
||||
WORD wNetType; |
||||
DWORD dwPrinters; |
||||
DWORD dwDrives; |
||||
} |
||||
} |
||||
|
||||
# TBD - is there a Tcl wrapper around NetShareCheck? |
||||
|
||||
# Create a network share |
||||
proc twapi::new_share {sharename path args} { |
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
{type.arg "file"} |
||||
{comment.arg ""} |
||||
{max_conn.int -1} |
||||
secd.arg |
||||
} -maxleftover 0] |
||||
|
||||
# If no security descriptor specified, default to "Everyone, |
||||
# read permission". Levaing it empty will give everyone all permissions |
||||
# which is probably not a good idea! |
||||
if {![info exists opts(secd)]} { |
||||
set opts(secd) [new_security_descriptor -dacl [new_acl [list [new_ace allow S-1-1-0 1179817]]]] |
||||
} |
||||
|
||||
NetShareAdd $opts(system) \ |
||||
$sharename \ |
||||
[_share_type_symbols_to_code $opts(type)] \ |
||||
$opts(comment) \ |
||||
$opts(max_conn) \ |
||||
[file nativename $path] \ |
||||
$opts(secd) |
||||
} |
||||
|
||||
# Delete a network share |
||||
proc twapi::delete_share {sharename args} { |
||||
array set opts [parseargs args {system.arg} -nulldefault] |
||||
NetShareDel $opts(system) $sharename 0 |
||||
} |
||||
|
||||
# Enumerate network shares |
||||
proc twapi::get_shares {args} { |
||||
|
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
{type.arg ""} |
||||
excludespecial |
||||
level.int |
||||
} -maxleftover 0] |
||||
|
||||
if {$opts(type) != ""} { |
||||
set type_filter [_share_type_symbols_to_code $opts(type) 1] |
||||
} |
||||
|
||||
if {[info exists opts(level)] && $opts(level) > 0} { |
||||
set level $opts(level) |
||||
} else { |
||||
# Either -level not specified or specified as 0 |
||||
# We need at least level 1 to filter on type |
||||
set level 1 |
||||
} |
||||
|
||||
set record_proc SHARE_INFO_$level |
||||
set raw_data [_net_enum_helper NetShareEnum -system $opts(system) -level $level -fields [$record_proc]] |
||||
set recs [list ] |
||||
foreach rec [recordarray getlist $raw_data] { |
||||
# 0xC0000000 -> 0x80000000 (STYPE_SPECIAL), 0x40000000 (STYPE_TEMPORARY) |
||||
set special [expr {[$record_proc -type $rec] & 0xC0000000}] |
||||
if {$special && $opts(excludespecial)} { |
||||
continue |
||||
} |
||||
# We need the special cast to int because else operands get promoted |
||||
# to 64 bits as the hex is treated as an unsigned value |
||||
set share_type [$record_proc -type $rec] |
||||
if {[info exists type_filter] && [expr {int($share_type & ~ $special)}] != $type_filter} { |
||||
continue |
||||
} |
||||
set rec [$record_proc set $rec -type [_share_type_code_to_symbols $share_type]] |
||||
if {[info exists opts(level)]} { |
||||
lappend recs $rec |
||||
} else { |
||||
lappend recs [$record_proc -name $rec] |
||||
} |
||||
} |
||||
|
||||
if {[info exists opts(level)]} { |
||||
set ra [list [$record_proc] $recs] |
||||
if {$opts(level) == 0} { |
||||
# We actually need only a level 0 subset |
||||
return [recordarray get $ra -slice [SHARE_INFO_0]] |
||||
} |
||||
return $ra |
||||
} else { |
||||
return $recs |
||||
} |
||||
} |
||||
|
||||
|
||||
# Get details about a share |
||||
proc twapi::get_share_info {sharename args} { |
||||
array set opts [parseargs args { |
||||
system.arg |
||||
all |
||||
name |
||||
type |
||||
path |
||||
comment |
||||
max_conn |
||||
current_conn |
||||
secd |
||||
} -nulldefault -hyphenated] |
||||
|
||||
set level 0 |
||||
|
||||
if {$opts(-all) || $opts(-name) || $opts(-type) || $opts(-comment)} { |
||||
set level 1 |
||||
set record_proc SHARE_INFO_1 |
||||
} |
||||
|
||||
if {$opts(-all) || $opts(-max_conn) || $opts(-current_conn) || $opts(-path)} { |
||||
set level 2 |
||||
set record_proc SHARE_INFO_2 |
||||
} |
||||
|
||||
if {$opts(-all) || $opts(-secd)} { |
||||
set level 502 |
||||
set record_proc SHARE_INFO_502 |
||||
} |
||||
|
||||
if {! $level} { |
||||
return |
||||
} |
||||
|
||||
set rec [NetShareGetInfo $opts(-system) $sharename $level] |
||||
set result [list ] |
||||
foreach opt {-name -comment -max_conn -current_conn -path -secd} { |
||||
if {$opts(-all) || $opts($opt)} { |
||||
lappend result $opt [$record_proc $opt $rec] |
||||
} |
||||
} |
||||
if {$opts(-all) || $opts(-type)} { |
||||
lappend result -type [_share_type_code_to_symbols [$record_proc -type $rec]] |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
|
||||
# Set a share configuration |
||||
proc twapi::set_share_info {sharename args} { |
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
comment.arg |
||||
max_conn.int |
||||
secd.arg |
||||
}] |
||||
|
||||
# First get the current config so we can change specified fields |
||||
# and write back |
||||
array set shareinfo [get_share_info $sharename -system $opts(system) \ |
||||
-comment -max_conn -secd] |
||||
foreach field {comment max_conn secd} { |
||||
if {[info exists opts($field)]} { |
||||
set shareinfo(-$field) $opts($field) |
||||
} |
||||
} |
||||
|
||||
NetShareSetInfo $opts(system) $sharename $shareinfo(-comment) \ |
||||
$shareinfo(-max_conn) $shareinfo(-secd) |
||||
} |
||||
|
||||
|
||||
# Get list of remote shares |
||||
proc twapi::get_client_shares {args} { |
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
level.int |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(level)]} { |
||||
set rec_proc USE_INFO_$opts(level) |
||||
set ra [_net_enum_helper NetUseEnum -system $opts(system) -level $opts(level) -fields [$rec_proc]] |
||||
set fields [$rec_proc] |
||||
set have_status [expr {"-status" in $fields}] |
||||
set have_type [expr {"-type" in $fields}] |
||||
if {! ($have_status || $have_type)} { |
||||
return $ra |
||||
} |
||||
set recs {} |
||||
foreach rec [recordarray getlist $ra] { |
||||
if {$have_status} { |
||||
set rec [$rec_proc set $rec -status [_map_useinfo_status [$rec_proc -status $rec]]] |
||||
} |
||||
if {$have_type} { |
||||
set rec [$rec_proc set $rec -type [_map_useinfo_type [$rec_proc -type $rec]]] |
||||
} |
||||
lappend recs $rec |
||||
} |
||||
return [list $fields $recs] |
||||
} |
||||
|
||||
# -level not specified. Just return a list of the remote share names |
||||
return [recordarray column [_net_enum_helper NetUseEnum -system $opts(system) -level 0 -fields [USE_INFO_0]] -remoteshare] |
||||
} |
||||
|
||||
|
||||
# Connect to a share |
||||
proc twapi::connect_share {remoteshare args} { |
||||
array set opts [parseargs args { |
||||
{type.arg "disk"} |
||||
localdevice.arg |
||||
provider.arg |
||||
password.arg |
||||
nopassword |
||||
defaultpassword |
||||
user.arg |
||||
{window.arg 0} |
||||
{interactive {} 0x8} |
||||
{prompt {} 0x10} |
||||
{updateprofile {} 0x1} |
||||
{commandline {} 0x800} |
||||
} -nulldefault] |
||||
|
||||
set flags 0 |
||||
|
||||
switch -exact -- $opts(type) { |
||||
"any" {set type 0} |
||||
"disk" - |
||||
"file" {set type 1} |
||||
"printer" {set type 2} |
||||
default { |
||||
error "Invalid network share type '$opts(type)'" |
||||
} |
||||
} |
||||
|
||||
# localdevice - "" means no local device, * means pick any, otherwise |
||||
# it's a local device to be mapped |
||||
if {$opts(localdevice) == "*"} { |
||||
set opts(localdevice) "" |
||||
setbits flags 0x80; # CONNECT_REDIRECT |
||||
} |
||||
|
||||
if {$opts(defaultpassword) && $opts(nopassword)} { |
||||
error "Options -defaultpassword and -nopassword may not be used together" |
||||
} |
||||
if {$opts(nopassword)} { |
||||
set opts(password) "" |
||||
set ignore_password 1 |
||||
} else { |
||||
set ignore_password 0 |
||||
if {$opts(defaultpassword)} { |
||||
set opts(password) "" |
||||
} |
||||
} |
||||
|
||||
set flags [expr {$flags | $opts(interactive) | $opts(prompt) | |
||||
$opts(updateprofile) | $opts(commandline)}] |
||||
|
||||
return [Twapi_WNetUseConnection $opts(window) $type $opts(localdevice) \ |
||||
$remoteshare $opts(provider) $opts(user) $ignore_password \ |
||||
$opts(password) $flags] |
||||
} |
||||
|
||||
# Disconnects an existing share |
||||
proc twapi::disconnect_share {sharename args} { |
||||
array set opts [parseargs args {updateprofile force}] |
||||
|
||||
set flags [expr {$opts(updateprofile) ? 0x1 : 0}] |
||||
WNetCancelConnection2 $sharename $flags $opts(force) |
||||
} |
||||
|
||||
|
||||
# Get information about a connected share |
||||
proc twapi::get_client_share_info {sharename args} { |
||||
if {$sharename eq ""} { |
||||
error "A share name cannot be the empty string" |
||||
} |
||||
|
||||
# We have to use a combination of NetUseGetInfo and |
||||
# WNetGetResourceInformation as neither gives us the full information |
||||
# THe former takes the local device name if there is one and will |
||||
# only accept a UNC if there is an entry for the UNC with |
||||
# no local device mapped. The latter |
||||
# always wants the UNC. So we need to figure out exactly if there |
||||
# is a local device mapped to the sharename or not |
||||
# TBD _ see if this is really the case. Also, NetUse only works with |
||||
# LANMAN, not WebDAV. So see if there is a way to only use WNet* |
||||
# variants |
||||
|
||||
# There may be multiple entries for the same UNC |
||||
# If there is an entry for the UNC with no device mapped, select |
||||
# that else select any of the local devices mapped to it |
||||
# TBD - any better way of finding out a mapping than calling |
||||
# get_client_shares? |
||||
# TBD - use wnet_connected_resources |
||||
foreach {elem_device elem_unc} [recordarray getlist [get_client_shares -level 0] -format flat] { |
||||
if {[string equal -nocase $sharename $elem_unc]} { |
||||
if {$elem_device eq ""} { |
||||
# Found an entry without a local device. Use it |
||||
set unc $elem_unc |
||||
unset -nocomplain local; # In case we found a match earlier |
||||
break |
||||
} else { |
||||
# Found a matching device |
||||
set local $elem_device |
||||
set unc $elem_unc |
||||
# Keep looping in case we find an entry with no local device |
||||
# (which we will prefer) |
||||
} |
||||
} else { |
||||
# See if the sharename is actually a local device name |
||||
if {[string equal -nocase [string trimright $elem_device :] [string trimright $sharename :]]} { |
||||
# Device name matches. Use it |
||||
set local $elem_device |
||||
set unc $elem_unc |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
if {![info exists unc]} { |
||||
win32_error 2250 "Share '$sharename' not found." |
||||
} |
||||
|
||||
# At this point $unc is the UNC form of the share and |
||||
# $local is either undefined or the local mapped device if there is one |
||||
|
||||
array set opts [parseargs args { |
||||
user |
||||
localdevice |
||||
remoteshare |
||||
status |
||||
type |
||||
opencount |
||||
usecount |
||||
domain |
||||
provider |
||||
comment |
||||
all |
||||
} -maxleftover 0 -hyphenated] |
||||
|
||||
|
||||
# Call Twapi_NetGetInfo always to get status. If we are not connected, |
||||
# we will not call WNetGetResourceInformation as that will time out |
||||
if {[info exists local]} { |
||||
set share [NetUseGetInfo "" $local 2] |
||||
} else { |
||||
set share [NetUseGetInfo "" $unc 2] |
||||
} |
||||
array set shareinfo [USE_INFO_2 $share] |
||||
unset shareinfo(-password) |
||||
if {[info exists shareinfo(-status)]} { |
||||
set shareinfo(-status) [_map_useinfo_status $shareinfo(-status)] |
||||
} |
||||
if {[info exists shareinfo(-type)]} { |
||||
set shareinfo(-type) [_map_useinfo_type $shareinfo(-type)] |
||||
} |
||||
|
||||
if {$opts(-all) || $opts(-comment) || $opts(-provider)} { |
||||
# Only get this information if we are connected |
||||
if {$shareinfo(-status) eq "connected"} { |
||||
set wnetinfo [lindex [Twapi_WNetGetResourceInformation $unc "" 0] 0] |
||||
set shareinfo(-comment) [lindex $wnetinfo 6] |
||||
set shareinfo(-provider) [lindex $wnetinfo 7] |
||||
} else { |
||||
set shareinfo(-comment) "" |
||||
set shareinfo(-provider) "" |
||||
} |
||||
} |
||||
|
||||
if {$opts(-all)} { |
||||
return [array get shareinfo] |
||||
} |
||||
|
||||
# Get rid of unwanted fields |
||||
foreach opt { |
||||
-user |
||||
-localdevice |
||||
-remoteshare |
||||
-status |
||||
-type |
||||
-opencount |
||||
-usecount |
||||
-domain |
||||
-provider |
||||
-comment |
||||
} { |
||||
if {! $opts($opt)} { |
||||
unset -nocomplain shareinfo($opt) |
||||
} |
||||
} |
||||
|
||||
return [array get shareinfo] |
||||
} |
||||
|
||||
|
||||
# Enumerate sessions |
||||
proc twapi::find_lm_sessions args { |
||||
array set opts [parseargs args { |
||||
all |
||||
{matchclient.arg ""} |
||||
{system.arg ""} |
||||
{matchuser.arg ""} |
||||
transport |
||||
clientname |
||||
user |
||||
clienttype |
||||
opencount |
||||
idleseconds |
||||
activeseconds |
||||
attrs |
||||
} -maxleftover 0] |
||||
|
||||
set level [_calc_minimum_session_info_level opts] |
||||
|
||||
# On all platforms, client must be in UNC format |
||||
set opts(matchclient) [_make_unc_computername $opts(matchclient)] |
||||
|
||||
trap { |
||||
set sessions [_net_enum_helper NetSessionEnum -system $opts(system) -preargs [list $opts(matchclient) $opts(matchuser)] -level $level -fields [SESSION_INFO_$level]] |
||||
} onerror {TWAPI_WIN32 2312} { |
||||
# No session matching the specified client |
||||
set sessions {} |
||||
} onerror {TWAPI_WIN32 2221} { |
||||
# No session matching the user |
||||
set sessions {} |
||||
} |
||||
|
||||
return [_format_lm_sessions $sessions opts] |
||||
} |
||||
|
||||
|
||||
# Get information about a session |
||||
proc twapi::get_lm_session_info {client user args} { |
||||
array set opts [parseargs args { |
||||
all |
||||
{system.arg ""} |
||||
transport |
||||
clientname |
||||
user |
||||
clienttype |
||||
opencount |
||||
idleseconds |
||||
activeseconds |
||||
attrs |
||||
} -maxleftover 0] |
||||
|
||||
set level [_calc_minimum_session_info_level opts] |
||||
if {$level == -1} { |
||||
# No data requested so return empty list |
||||
return [list ] |
||||
} |
||||
|
||||
if {![min_os_version 5]} { |
||||
# System name is specified. If NT, make sure it is UNC form |
||||
set opts(system) [_make_unc_computername $opts(system)] |
||||
} |
||||
|
||||
# On all platforms, client must be in UNC format |
||||
set client [_make_unc_computername $client] |
||||
|
||||
# Note an error is generated if no matching session exists |
||||
set sess [NetSessionGetInfo $opts(system) $client $user $level] |
||||
|
||||
return [recordarray index [_format_lm_sessions [list [SESSION_INFO_$level] [list $sess]] opts] 0 -format dict] |
||||
} |
||||
|
||||
# Delete sessions |
||||
proc twapi::end_lm_sessions args { |
||||
array set opts [parseargs args { |
||||
{client.arg ""} |
||||
{system.arg ""} |
||||
{user.arg ""} |
||||
} -maxleftover 0] |
||||
|
||||
if {![min_os_version 5]} { |
||||
# System name is specified. If NT, make sure it is UNC form |
||||
set opts(system) [_make_unc_computername $opts(system)] |
||||
} |
||||
|
||||
if {$opts(client) eq "" && $opts(user) eq ""} { |
||||
win32_error 87 "At least one of -client and -user must be specified." |
||||
} |
||||
|
||||
# On all platforms, client must be in UNC format |
||||
set opts(client) [_make_unc_computername $opts(client)] |
||||
|
||||
trap { |
||||
NetSessionDel $opts(system) $opts(client) $opts(user) |
||||
} onerror {TWAPI_WIN32 2312} { |
||||
# No session matching the specified client - ignore error |
||||
} onerror {TWAPI_WIN32 2221} { |
||||
# No session matching the user - ignore error |
||||
} |
||||
return |
||||
} |
||||
|
||||
# Enumerate open files |
||||
proc twapi::find_lm_open_files args { |
||||
array set opts [parseargs args { |
||||
{basepath.arg ""} |
||||
{system.arg ""} |
||||
{matchuser.arg ""} |
||||
all |
||||
permissions |
||||
id |
||||
lockcount |
||||
path |
||||
user |
||||
} -maxleftover 0] |
||||
|
||||
set level 3 |
||||
if {! ($opts(all) || $opts(permissions) || $opts(lockcount) || |
||||
$opts(path) || $opts(user))} { |
||||
# Only id's required |
||||
set level 2 |
||||
} |
||||
|
||||
# TBD - change to use -resume option to _net_enum_helper as there |
||||
# might be a lot of files |
||||
trap { |
||||
set files [_net_enum_helper NetFileEnum -system $opts(system) -preargs [list [file nativename $opts(basepath)] $opts(matchuser)] -level $level -fields [FILE_INFO_$level]] |
||||
} onerror {TWAPI_WIN32 2221} { |
||||
# No files matching the user |
||||
set files [list [FILE_INFO_$level] {}] |
||||
} |
||||
|
||||
return [_format_lm_open_files $files opts] |
||||
} |
||||
|
||||
# Get information about an open LM file |
||||
proc twapi::get_lm_open_file_info {fid args} { |
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
all |
||||
permissions |
||||
id |
||||
lockcount |
||||
path |
||||
user |
||||
} -maxleftover 0] |
||||
|
||||
# System name is specified. If NT, make sure it is UNC form |
||||
if {![min_os_version 5]} { |
||||
set opts(system) [_make_unc_computername $opts(system)] |
||||
} |
||||
|
||||
set level 3 |
||||
if {! ($opts(all) || $opts(permissions) || $opts(lockcount) || |
||||
$opts(path) || $opts(user))} { |
||||
# Only id's required. We actually already have this but don't |
||||
# return it since we want to go ahead and make the call in case |
||||
# the id does not exist |
||||
set level 2 |
||||
} |
||||
|
||||
return [recordarray index [_format_lm_open_files [list [FILE_INFO_$level] [list [NetFileGetInfo $opts(system) $fid $level]]] opts] 0 -format dict] |
||||
} |
||||
|
||||
# Close an open LM file |
||||
proc twapi::close_lm_open_file {fid args} { |
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
} -maxleftover 0] |
||||
trap { |
||||
NetFileClose $opts(system) $fid |
||||
} onerror {TWAPI_WIN32 2314} { |
||||
# No such fid. Ignore, perhaps it was closed in the meanwhile |
||||
} |
||||
} |
||||
|
||||
|
||||
# Enumerate open connections |
||||
proc twapi::find_lm_connections args { |
||||
array set opts [parseargs args { |
||||
client.arg |
||||
{system.arg ""} |
||||
share.arg |
||||
all |
||||
id |
||||
type |
||||
opencount |
||||
usercount |
||||
activeseconds |
||||
user |
||||
clientname |
||||
sharename |
||||
} -maxleftover 0] |
||||
|
||||
if {! ([info exists opts(client)] || [info exists opts(share)])} { |
||||
win32_error 87 "Must specify either -client or -share option." |
||||
} |
||||
|
||||
if {[info exists opts(client)] && [info exists opts(share)]} { |
||||
win32_error 87 "Must not specify both -client and -share options." |
||||
} |
||||
|
||||
if {[info exists opts(client)]} { |
||||
set qualifier [_make_unc_computername $opts(client)] |
||||
} else { |
||||
set qualifier $opts(share) |
||||
} |
||||
|
||||
set level 0 |
||||
if {$opts(all) || $opts(type) || $opts(opencount) || |
||||
$opts(usercount) || $opts(user) || |
||||
$opts(activeseconds) || $opts(clientname) || $opts(sharename)} { |
||||
set level 1 |
||||
} |
||||
|
||||
# TBD - change to use -resume option to _net_enum_helper since |
||||
# there might be a log of connections |
||||
set conns [_net_enum_helper NetConnectionEnum -system $opts(system) -preargs [list $qualifier] -level $level -fields [CONNECTION_INFO_$level]] |
||||
|
||||
# NOTE fields MUST BE IN SAME ORDER AS VALUES BELOW |
||||
if {! $opts(all)} { |
||||
set fields {} |
||||
foreach opt {id opencount usercount activeseconds user type} { |
||||
if {$opts(all) || $opts($opt)} { |
||||
lappend fields -$opt |
||||
} |
||||
} |
||||
if {$opts(all) || $opts(clientname) || $opts(sharename)} { |
||||
lappend fields -netname |
||||
} |
||||
set conns [recordarray get $conns -slice $fields] |
||||
} |
||||
set fields [recordarray fields $conns] |
||||
if {"-type" in $fields} { |
||||
set type_enum [enum $fields -type] |
||||
} |
||||
if {"-netname" in $fields} { |
||||
set netname_enum [enum $fields -netname] |
||||
} |
||||
|
||||
if {! ([info exists type_enum] || [info exists netname_enum])} { |
||||
# No need to massage any data |
||||
return $conns |
||||
} |
||||
|
||||
set recs {} |
||||
foreach rec [recordarray getlist $conns] { |
||||
if {[info exists type_enum]} { |
||||
lset rec $type_enum [_share_type_code_to_symbols [lindex $rec $type_enum]] |
||||
} |
||||
if {[info exists netname_enum]} { |
||||
# What's returned in the netname field depends on what we |
||||
# passed as the qualifier |
||||
if {[info exists opts(client)]} { |
||||
set sharename [lindex $rec $netname_enum] |
||||
set clientname [_make_unc_computername $opts(client)] |
||||
} else { |
||||
set sharename $opts(share) |
||||
set clientname [_make_unc_computername [lindex $rec $netname_enum]] |
||||
} |
||||
if {$opts(all) || $opts(clientname)} { |
||||
lappend rec $clientname |
||||
} |
||||
if {$opts(all) || $opts(sharename)} { |
||||
lappend rec $sharename |
||||
} |
||||
} |
||||
lappend recs $rec |
||||
} |
||||
if {$opts(all) || $opts(clientname)} { |
||||
lappend fields -clientname |
||||
} |
||||
if {$opts(all) || $opts(sharename)} { |
||||
lappend fields -sharename |
||||
} |
||||
|
||||
return [list $fields $recs] |
||||
} |
||||
|
||||
proc twapi::wnet_connected_resources {args} { |
||||
# Accept both file/disk and print/printer for historical reasons |
||||
# file and printer are official to match get_client_share_info |
||||
parseargs args { |
||||
{type.sym any {any 0 file 1 disk 1 print 2 printer 2}} |
||||
} -maxleftover 0 -setvars |
||||
set h [WNetOpenEnum 1 $type 0 ""] |
||||
trap { |
||||
set resources {} |
||||
set structdef [twapi::NETRESOURCE] |
||||
while {[llength [set rs [WNetEnumResource $h 100 $structdef]]]} { |
||||
foreach r $rs { |
||||
lappend resources [lrange $r 4 5] |
||||
} |
||||
} |
||||
} finally { |
||||
WNetCloseEnum $h |
||||
} |
||||
return $resources |
||||
} |
||||
|
||||
################################################################ |
||||
# Utility functions |
||||
|
||||
# Common code to figure out what SESSION_INFO level is required |
||||
# for the specified set of requested fields. v_opts is name |
||||
# of array indicating which fields are required |
||||
proc twapi::_calc_minimum_session_info_level {v_opts} { |
||||
upvar $v_opts opts |
||||
|
||||
# Set the information level requested based on options specified. |
||||
# We set the level to the one that requires the lowest possible |
||||
# privilege level and still includes the data requested. |
||||
if {$opts(all) || $opts(transport)} { |
||||
return 502 |
||||
} elseif {$opts(clienttype)} { |
||||
return 2 |
||||
} elseif {$opts(opencount) || $opts(attrs)} { |
||||
return 1 |
||||
} elseif {$opts(clientname) || $opts(user) || |
||||
$opts(idleseconds) || $opts(activeseconds)} { |
||||
return 10 |
||||
} else { |
||||
return 0 |
||||
} |
||||
} |
||||
|
||||
# Common code to format a session record. v_opts is name of array |
||||
# that controls which fields are returned |
||||
# sessions is a record array |
||||
proc twapi::_format_lm_sessions {sessions v_opts} { |
||||
upvar $v_opts opts |
||||
|
||||
if {! $opts(all)} { |
||||
set fields {} |
||||
foreach opt { |
||||
transport user opencount idleseconds activeseconds |
||||
clienttype clientname attrs |
||||
} { |
||||
if {$opts(all) || $opts($opt)} { |
||||
lappend fields -$opt |
||||
} |
||||
} |
||||
set sessions [recordarray get $sessions -slice $fields] |
||||
} |
||||
|
||||
set fields [recordarray fields $sessions] |
||||
if {"-clientname" in $fields} { |
||||
set client_enum [enum $fields -clientname] |
||||
} |
||||
if {"-attrs" in $fields} { |
||||
set attrs_enum [enum $fields -attrs] |
||||
} |
||||
|
||||
if {! ([info exists client_enum] || [info exists attrs_enum])} { |
||||
return $sessions |
||||
} |
||||
|
||||
# Need to map client name and attrs fields |
||||
set recs {} |
||||
foreach rec [recordarray getlist $sessions] { |
||||
if {[info exists client_enum]} { |
||||
lset rec $client_enum [_make_unc_computername [lindex $rec $client_enum]] |
||||
} |
||||
if {[info exists attrs_enum]} { |
||||
set attrs {} |
||||
set flags [lindex $rec $attrs_enum] |
||||
if {$flags & 1} { |
||||
lappend attrs guest |
||||
} |
||||
if {$flags & 2} { |
||||
lappend attrs noencryption |
||||
} |
||||
lset rec $attrs_enum $attrs |
||||
} |
||||
lappend recs $rec |
||||
} |
||||
return [list $fields $recs] |
||||
} |
||||
|
||||
# Common code to format a lm open file record. v_opts is name of array |
||||
# that controls which fields are returned |
||||
proc twapi::_format_lm_open_files {files v_opts} { |
||||
upvar $v_opts opts |
||||
|
||||
if {! $opts(all)} { |
||||
set fields {} |
||||
foreach opt { |
||||
id lockcount path user permissions |
||||
} { |
||||
if {$opts(all) || $opts($opt)} { |
||||
lappend fields -$opt |
||||
} |
||||
} |
||||
set files [recordarray get $files -slice $fields] |
||||
} |
||||
|
||||
set fields [recordarray fields $files] |
||||
|
||||
if {"-permissions" ni $fields} { |
||||
return $files |
||||
} |
||||
|
||||
# Need to massage permissions |
||||
set enum [enum $fields -permissions] |
||||
|
||||
set recs {} |
||||
foreach rec [recordarray getlist $files] { |
||||
set permissions [list ] |
||||
set perms [lindex $rec $enum] |
||||
foreach {flag perm} {1 read 2 write 4 create} { |
||||
if {$perms & $flag} { |
||||
lappend permissions $perm |
||||
} |
||||
} |
||||
lset rec $enum $permissions |
||||
lappend recs $rec |
||||
} |
||||
|
||||
return [list $fields $recs] |
||||
} |
||||
|
||||
# NOTE: THIS ONLY MAPS FOR THE Net* functions, NOT THE WNet* |
||||
proc twapi::_share_type_symbols_to_code {typesyms {basetypeonly 0}} { |
||||
|
||||
# STYPE_DISKTREE 0 |
||||
# STYPE_PRINTQ 1 |
||||
# STYPE_DEVICE 2 |
||||
# STYPE_IPC 3 |
||||
switch -exact -- [lindex $typesyms 0] { |
||||
file { set code 0 } |
||||
printer { set code 1 } |
||||
device { set code 2 } |
||||
ipc { set code 3 } |
||||
default { |
||||
error "Unknown type network share type symbol [lindex $typesyms 0]" |
||||
} |
||||
} |
||||
|
||||
if {$basetypeonly} { |
||||
return $code |
||||
} |
||||
|
||||
# STYPE_TEMPORARY 0x40000000 |
||||
# STYPE_SPECIAL 0x80000000 |
||||
set special 0 |
||||
foreach sym [lrange $typesyms 1 end] { |
||||
switch -exact -- $sym { |
||||
special { setbits special 0x80000000 } |
||||
temporary { setbits special 0x40000000 } |
||||
file - |
||||
printer - |
||||
device - |
||||
ipc { |
||||
error "Base share type symbol '$sym' cannot be used as a share attribute type" |
||||
} |
||||
default { |
||||
error "Unknown type network share type symbol '$sym'" |
||||
} |
||||
} |
||||
} |
||||
|
||||
return [expr {$code | $special}] |
||||
} |
||||
|
||||
|
||||
# First element is always the base type of the share |
||||
# NOTE: THIS ONLY MAPS FOR THE Net* functions, NOT THE WNet* |
||||
proc twapi::_share_type_code_to_symbols {type} { |
||||
|
||||
# STYPE_DISKTREE 0 |
||||
# STYPE_PRINTQ 1 |
||||
# STYPE_DEVICE 2 |
||||
# STYPE_IPC 3 |
||||
# STYPE_TEMPORARY 0x40000000 |
||||
# STYPE_SPECIAL 0x80000000 |
||||
|
||||
set special [expr {$type & 0xC0000000}] |
||||
|
||||
# We need the special cast to int because else operands get promoted |
||||
# to 64 bits as the hex is treated as an unsigned value |
||||
switch -exact -- [expr {int($type & ~ $special)}] { |
||||
0 {set sym "file"} |
||||
1 {set sym "printer"} |
||||
2 {set sym "device"} |
||||
3 {set sym "ipc"} |
||||
default {set sym $type} |
||||
} |
||||
|
||||
set typesyms [list $sym] |
||||
|
||||
if {$special & 0x80000000} { |
||||
lappend typesyms special |
||||
} |
||||
|
||||
if {$special & 0x40000000} { |
||||
lappend typesyms temporary |
||||
} |
||||
|
||||
return $typesyms |
||||
} |
||||
|
||||
# Make sure a computer name is in unc format unless it is an empty |
||||
# string (local computer) |
||||
proc twapi::_make_unc_computername {name} { |
||||
if {$name eq ""} { |
||||
return "" |
||||
} else { |
||||
return "\\\\[string trimleft $name \\]" |
||||
} |
||||
} |
||||
|
||||
proc twapi::_map_useinfo_status {status} { |
||||
set sym [lindex {connected paused lostsession disconnected networkerror connecting reconnecting} $status] |
||||
if {$sym ne ""} { |
||||
return $sym |
||||
} else { |
||||
return $status |
||||
} |
||||
} |
||||
|
||||
proc twapi::_map_useinfo_type {type} { |
||||
# Note share type and use info types are different |
||||
return [_share_type_code_to_symbols [expr {$type & 0x3fffffff}]] |
||||
} |
@ -0,0 +1,627 @@
|
||||
# |
||||
# Copyright (c) 2004-2011 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi {} |
||||
|
||||
|
||||
# Get the specified shell folder |
||||
proc twapi::get_shell_folder {csidl args} { |
||||
variable csidl_lookup |
||||
|
||||
array set opts [parseargs args {create} -maxleftover 0] |
||||
|
||||
# Following are left out because they refer to virtual folders |
||||
# and will return error if used here |
||||
# CSIDL_BITBUCKET - 0xa |
||||
if {![info exists csidl_lookup]} { |
||||
array set csidl_lookup { |
||||
CSIDL_ADMINTOOLS 0x30 |
||||
CSIDL_COMMON_ADMINTOOLS 0x2f |
||||
CSIDL_APPDATA 0x1a |
||||
CSIDL_COMMON_APPDATA 0x23 |
||||
CSIDL_COMMON_DESKTOPDIRECTORY 0x19 |
||||
CSIDL_COMMON_DOCUMENTS 0x2e |
||||
CSIDL_COMMON_FAVORITES 0x1f |
||||
CSIDL_COMMON_MUSIC 0x35 |
||||
CSIDL_COMMON_PICTURES 0x36 |
||||
CSIDL_COMMON_PROGRAMS 0x17 |
||||
CSIDL_COMMON_STARTMENU 0x16 |
||||
CSIDL_COMMON_STARTUP 0x18 |
||||
CSIDL_COMMON_TEMPLATES 0x2d |
||||
CSIDL_COMMON_VIDEO 0x37 |
||||
CSIDL_COOKIES 0x21 |
||||
CSIDL_DESKTOPDIRECTORY 0x10 |
||||
CSIDL_FAVORITES 0x6 |
||||
CSIDL_HISTORY 0x22 |
||||
CSIDL_INTERNET_CACHE 0x20 |
||||
CSIDL_LOCAL_APPDATA 0x1c |
||||
CSIDL_MYMUSIC 0xd |
||||
CSIDL_MYPICTURES 0x27 |
||||
CSIDL_MYVIDEO 0xe |
||||
CSIDL_NETHOOD 0x13 |
||||
CSIDL_PERSONAL 0x5 |
||||
CSIDL_PRINTHOOD 0x1b |
||||
CSIDL_PROFILE 0x28 |
||||
CSIDL_PROFILES 0x3e |
||||
CSIDL_PROGRAMS 0x2 |
||||
CSIDL_PROGRAM_FILES 0x26 |
||||
CSIDL_PROGRAM_FILES_COMMON 0x2b |
||||
CSIDL_RECENT 0x8 |
||||
CSIDL_SENDTO 0x9 |
||||
CSIDL_STARTMENU 0xb |
||||
CSIDL_STARTUP 0x7 |
||||
CSIDL_SYSTEM 0x25 |
||||
CSIDL_TEMPLATES 0x15 |
||||
CSIDL_WINDOWS 0x24 |
||||
CSIDL_CDBURN_AREA 0x3b |
||||
} |
||||
} |
||||
|
||||
if {![string is integer $csidl]} { |
||||
set csidl_key [string toupper $csidl] |
||||
if {![info exists csidl_lookup($csidl_key)]} { |
||||
# Try by adding a CSIDL prefix |
||||
set csidl_key "CSIDL_$csidl_key" |
||||
if {![info exists csidl_lookup($csidl_key)]} { |
||||
error "Invalid CSIDL value '$csidl'" |
||||
} |
||||
} |
||||
set csidl $csidl_lookup($csidl_key) |
||||
} |
||||
|
||||
trap { |
||||
set path [SHGetSpecialFolderPath 0 $csidl $opts(create)] |
||||
} onerror {} { |
||||
# Try some other way to get the information |
||||
switch -exact -- [format %x $csidl] { |
||||
1a { catch {set path $::env(APPDATA)} } |
||||
2b { catch {set path $::env(CommonProgramFiles)} } |
||||
26 { catch {set path $::env(ProgramFiles)} } |
||||
24 { catch {set path $::env(windir)} } |
||||
25 { catch {set path [file join $::env(systemroot) system32]} } |
||||
} |
||||
if {![info exists path]} { |
||||
return "" |
||||
} |
||||
} |
||||
|
||||
return $path |
||||
} |
||||
|
||||
# Displays a shell property dialog for the given object |
||||
proc twapi::shell_object_properties_dialog {path args} { |
||||
array set opts [parseargs args { |
||||
{type.arg file {file printer volume}} |
||||
{hwin.int 0} |
||||
{page.arg ""} |
||||
} -maxleftover 0] |
||||
|
||||
|
||||
if {$opts(type) eq "file"} { |
||||
set path [file nativename [file normalize $path]] |
||||
} |
||||
|
||||
SHObjectProperties $opts(hwin) \ |
||||
[string map {printer 1 file 2 volume 4} $opts(type)] \ |
||||
$path \ |
||||
$opts(page) |
||||
} |
||||
|
||||
# Writes a shell shortcut |
||||
proc twapi::write_shortcut {link args} { |
||||
|
||||
array set opts [parseargs args { |
||||
path.arg |
||||
idl.arg |
||||
args.arg |
||||
desc.arg |
||||
hotkey.arg |
||||
iconpath.arg |
||||
iconindex.int |
||||
{showcmd.arg normal} |
||||
workdir.arg |
||||
relativepath.arg |
||||
runas.bool |
||||
} -nulldefault -maxleftover 0] |
||||
|
||||
# Map hot key to integer if needed |
||||
if {![string is integer -strict $opts(hotkey)]} { |
||||
if {$opts(hotkey) eq ""} { |
||||
set opts(hotkey) 0 |
||||
} else { |
||||
# Try treating it as symbolic |
||||
lassign [_hotkeysyms_to_vk $opts(hotkey)] modifiers vk |
||||
set opts(hotkey) $vk |
||||
if {$modifiers & 1} { |
||||
set opts(hotkey) [expr {$opts(hotkey) | (4<<8)}] |
||||
} |
||||
if {$modifiers & 2} { |
||||
set opts(hotkey) [expr {$opts(hotkey) | (2<<8)}] |
||||
} |
||||
if {$modifiers & 4} { |
||||
set opts(hotkey) [expr {$opts(hotkey) | (1<<8)}] |
||||
} |
||||
if {$modifiers & 8} { |
||||
set opts(hotkey) [expr {$opts(hotkey) | (8<<8)}] |
||||
} |
||||
} |
||||
} |
||||
|
||||
# IF a known symbol translate it. Note caller can pass integer |
||||
# values as well which will be kept as they are. Bogus valuse and |
||||
# symbols will generate an error on the actual call so we don't |
||||
# check here. |
||||
switch -exact -- $opts(showcmd) { |
||||
minimized { set opts(showcmd) 7 } |
||||
maximized { set opts(showcmd) 3 } |
||||
normal { set opts(showcmd) 1 } |
||||
} |
||||
|
||||
Twapi_WriteShortcut $link $opts(path) $opts(idl) $opts(args) \ |
||||
$opts(desc) $opts(hotkey) $opts(iconpath) $opts(iconindex) \ |
||||
$opts(relativepath) $opts(showcmd) $opts(workdir) $opts(runas) |
||||
} |
||||
|
||||
|
||||
# Read a shortcut |
||||
proc twapi::read_shortcut {link args} { |
||||
array set opts [parseargs args { |
||||
timeout.int |
||||
{hwin.int 0} |
||||
|
||||
{_comment {Path format flags}} |
||||
{shortnames {} 1} |
||||
{uncpath {} 2} |
||||
{rawpath {} 4} |
||||
|
||||
{_comment {Resolve flags}} |
||||
{install {} 128} |
||||
{nolinkinfo {} 64} |
||||
{notrack {} 32} |
||||
{nosearch {} 16} |
||||
{anymatch {} 2} |
||||
{noui {} 1} |
||||
} -maxleftover 0] |
||||
|
||||
set pathfmt [expr {$opts(shortnames) | $opts(uncpath) | $opts(rawpath)}] |
||||
|
||||
# 4 -> SLR_UPDATE |
||||
set resolve_flags [expr {4 | $opts(install) | $opts(nolinkinfo) | |
||||
$opts(notrack) | $opts(nosearch) | |
||||
$opts(anymatch) | $opts(noui)}] |
||||
|
||||
array set shortcut [twapi::Twapi_ReadShortcut $link $pathfmt $opts(hwin) $resolve_flags] |
||||
|
||||
switch -exact -- $shortcut(-showcmd) { |
||||
1 { set shortcut(-showcmd) normal } |
||||
3 { set shortcut(-showcmd) maximized } |
||||
7 { set shortcut(-showcmd) minimized } |
||||
} |
||||
|
||||
return [array get shortcut] |
||||
} |
||||
|
||||
|
||||
|
||||
# Writes a url shortcut |
||||
proc twapi::write_url_shortcut {link url args} { |
||||
|
||||
array set opts [parseargs args { |
||||
{missingprotocol.arg 0} |
||||
} -nulldefault -maxleftover 0] |
||||
|
||||
switch -exact -- $opts(missingprotocol) { |
||||
guess { |
||||
set opts(missingprotocol) 1; # IURL_SETURL_FL_GUESS_PROTOCOL |
||||
} |
||||
usedefault { |
||||
# 3 -> IURL_SETURL_FL_GUESS_PROTOCOL | IURL_SETURL_FL_USE_DEFAULT_PROTOCOL |
||||
# The former must also be specified (based on experimentation) |
||||
set opts(missingprotocol) 3 |
||||
} |
||||
default { |
||||
if {![string is integer -strict $opts(missingprotocol)]} { |
||||
error "Invalid value '$opts(missingprotocol)' for -missingprotocol option." |
||||
} |
||||
} |
||||
} |
||||
|
||||
Twapi_WriteUrlShortcut $link $url $opts(missingprotocol) |
||||
} |
||||
|
||||
# Read a url shortcut |
||||
proc twapi::read_url_shortcut {link} { |
||||
return [Twapi_ReadUrlShortcut $link] |
||||
} |
||||
|
||||
# Invoke a url shortcut |
||||
proc twapi::invoke_url_shortcut {link args} { |
||||
|
||||
array set opts [parseargs args { |
||||
verb.arg |
||||
{hwin.int 0} |
||||
allowui |
||||
} -maxleftover 0] |
||||
|
||||
set flags 0 |
||||
if {$opts(allowui)} {setbits flags 1} |
||||
if {! [info exists opts(verb)]} { |
||||
setbits flags 2 |
||||
set opts(verb) "" |
||||
} |
||||
|
||||
Twapi_InvokeUrlShortcut $link $opts(verb) $flags $opts(hwin) |
||||
} |
||||
|
||||
# Send a file to the recycle bin |
||||
proc twapi::recycle_file {fn args} { |
||||
return [recycle_files [list $fn] {*}$args] |
||||
} |
||||
|
||||
# Send multiple files to the recycle bin - from Alexandru |
||||
# This is much faster than "recycle_file"! |
||||
proc twapi::recycle_files {fns args} { |
||||
array set opts [parseargs args { |
||||
confirm.bool |
||||
showerror.bool |
||||
} -maxleftover 0 -nulldefault] |
||||
|
||||
if {$opts(confirm)} { |
||||
set flags 0x40; # FOF_ALLOWUNDO |
||||
} else { |
||||
set flags 0x50; # FOF_ALLOWUNDO | FOF_NOCONFIRMATION |
||||
} |
||||
|
||||
if {! $opts(showerror)} { |
||||
set flags [expr {$flags | 0x0400}]; # FOF_NOERRORUI |
||||
} |
||||
|
||||
set fns [lmap fn $fns { |
||||
file nativename [file normalize $fn] |
||||
}] |
||||
|
||||
return [expr {[lindex [Twapi_SHFileOperation 0 3 $fns __null__ $flags ""] 0] ? false : true}] |
||||
} |
||||
|
||||
proc twapi::shell_execute args { |
||||
# TBD - Document following shell_execute options after testing. |
||||
# [opt_def [cmd -connect] [arg BOOLEAN]] |
||||
# [opt_def [cmd -hicon] [arg HANDLE]] |
||||
# [opt_def [cmd -hkeyclass] [arg BOOLEAN]] |
||||
# [opt_def [cmd -hotkey] [arg HOTKEY]] |
||||
# [opt_def [cmd -nozonechecks] [arg BOOLEAN]] |
||||
|
||||
array set opts [parseargs args { |
||||
class.arg |
||||
dir.arg |
||||
{hicon.arg NULL} |
||||
{hkeyclass.arg NULL} |
||||
{hmonitor.arg NULL} |
||||
hotkey.arg |
||||
hwin.int |
||||
idl.arg |
||||
params.arg |
||||
path.arg |
||||
{show.arg 1} |
||||
verb.arg |
||||
|
||||
{getprocesshandle.bool 0 0x00000040} |
||||
{connect.bool 0 0x00000080} |
||||
{wait.bool 0x00000100 0x00000100} |
||||
{substenv.bool 0 0x00000200} |
||||
{noui.bool 0 0x00000400} |
||||
{unicode.bool 0 0x00004000} |
||||
{noconsole.bool 0 0x00008000} |
||||
{asyncok.bool 0 0x00100000} |
||||
{nozonechecks.bool 0 0x00800000} |
||||
{waitforinputidle.bool 0 0x02000000} |
||||
{logusage.bool 0 0x04000000} |
||||
{invokeidlist.bool 0 0x0000000C} |
||||
} -maxleftover 0 -nulldefault] |
||||
|
||||
set fmask 0 |
||||
|
||||
foreach {opt mask} { |
||||
class 1 |
||||
idl 4 |
||||
} { |
||||
if {$opts($opt) ne ""} { |
||||
setbits fmask $mask |
||||
} |
||||
} |
||||
|
||||
if {$opts(hkeyclass) ne "NULL"} { |
||||
setbits fmask 3 |
||||
} |
||||
|
||||
set fmask [expr {$fmask | |
||||
$opts(getprocesshandle) | $opts(connect) | $opts(wait) | |
||||
$opts(substenv) | $opts(noui) | $opts(unicode) | |
||||
$opts(noconsole) | $opts(asyncok) | $opts(nozonechecks) | |
||||
$opts(waitforinputidle) | $opts(logusage) | |
||||
$opts(invokeidlist)}] |
||||
|
||||
if {$opts(hicon) ne "NULL" && $opts(hmonitor) ne "NULL"} { |
||||
error "Cannot specify -hicon and -hmonitor options together." |
||||
} |
||||
|
||||
set hiconormonitor NULL |
||||
if {$opts(hicon) ne "NULL"} { |
||||
set hiconormonitor $opts(hicon) |
||||
set flags [expr {$flags | 0x00000010}] |
||||
} elseif {$opts(hmonitor) ne "NULL"} { |
||||
set hiconormonitor $opts(hmonitor) |
||||
set flags [expr {$flags | 0x00200000}] |
||||
} |
||||
|
||||
if {![string is integer -strict $opts(show)]} { |
||||
set opts(show) [dict get { |
||||
hide 0 |
||||
shownormal 1 |
||||
normal 1 |
||||
showminimized 2 |
||||
showmaximized 3 |
||||
maximize 3 |
||||
shownoactivate 4 |
||||
show 5 |
||||
minimize 6 |
||||
showminnoactive 7 |
||||
showna 8 |
||||
restore 9 |
||||
showdefault 10 |
||||
forceminimize 11 |
||||
} $opts(show)] |
||||
} |
||||
|
||||
if {$opts(hotkey) eq ""} { |
||||
set hotkey 0 |
||||
} else { |
||||
lassign [_hotkeysyms_to_vk $opts(hotkey) { |
||||
shift 1 |
||||
ctrl 2 |
||||
control 2 |
||||
alt 4 |
||||
menu 4 |
||||
ext 8 |
||||
}] modifiers vk |
||||
set hotkey [expr {($modifiers << 16) | $vk}] |
||||
} |
||||
if {$hotkey != 0} { |
||||
setbits fmask 0x00000020 |
||||
} |
||||
return [Twapi_ShellExecuteEx \ |
||||
$fmask \ |
||||
$opts(hwin) \ |
||||
$opts(verb) \ |
||||
$opts(path) \ |
||||
$opts(params) \ |
||||
$opts(dir) \ |
||||
$opts(show) \ |
||||
$opts(idl) \ |
||||
$opts(class) \ |
||||
$opts(hkeyclass) \ |
||||
$hotkey \ |
||||
$hiconormonitor] |
||||
} |
||||
|
||||
|
||||
namespace eval twapi::systemtray { |
||||
|
||||
namespace path [namespace parent] |
||||
|
||||
# Dictionary mapping id->handler, hicon |
||||
variable _icondata |
||||
set _icondata [dict create] |
||||
|
||||
variable _icon_id_ctr |
||||
|
||||
variable _message_map |
||||
array set _message_map { |
||||
123 contextmenu |
||||
512 mousemove |
||||
513 lbuttondown |
||||
514 lbuttonup |
||||
515 lbuttondblclk |
||||
516 rbuttondown |
||||
517 rbuttonup |
||||
518 rbuttondblclk |
||||
519 mbuttondown |
||||
520 mbuttonup |
||||
521 mbuttondblclk |
||||
522 mousewheel |
||||
523 xbuttondown |
||||
524 xbuttonup |
||||
525 xbuttondblclk |
||||
1024 select |
||||
1025 keyselect |
||||
1026 balloonshow |
||||
1027 balloonhide |
||||
1028 balloontimeout |
||||
1029 balloonuserclick |
||||
} |
||||
|
||||
proc _make_NOTIFYICONW {id args} { |
||||
# TBD - implement -hiddenicon and -sharedicon using |
||||
# dwState and dwStateMask |
||||
set state 0 |
||||
set statemask 0 |
||||
array set opts [parseargs args { |
||||
hicon.arg |
||||
tip.arg |
||||
balloon.arg |
||||
timeout.int |
||||
version.int |
||||
balloontitle.arg |
||||
{balloonicon.arg none {info warning error user none}} |
||||
{silent.bool 0} |
||||
} -maxleftover 0] |
||||
|
||||
set timeout_or_version 0 |
||||
if {[info exists opts(version)]} { |
||||
if {[info exists opts(timeout)]} { |
||||
error "Cannot simultaneously specify -timeout and -version." |
||||
} |
||||
set timeout_or_version $opts(version) |
||||
} else { |
||||
if {[info exists opts(timeout)]} { |
||||
set timeout_or_version $opts(timeout) |
||||
} |
||||
} |
||||
|
||||
set flags 0x1; # uCallbackMessage member is valid |
||||
if {[info exists opts(hicon)]} { |
||||
incr flags 0x2; # hIcon member is valid |
||||
} else { |
||||
set opts(hicon) NULL |
||||
} |
||||
|
||||
if {[info exists opts(tip)]} { |
||||
incr flags 0x4 |
||||
# Truncate if necessary to 127 chars |
||||
set opts(tip) [string range $opts(tip) 0 127] |
||||
} else { |
||||
set opts(tip) "" |
||||
} |
||||
|
||||
if {[info exists opts(balloon)] || [info exists opts(balloontitle)]} { |
||||
incr flags 0x10 |
||||
} |
||||
|
||||
if {[info exists opts(balloon)]} { |
||||
set opts(balloon) [string range $opts(balloon) 0 255] |
||||
} else { |
||||
set opts(balloon) "" |
||||
} |
||||
|
||||
if {[info exists opts(balloontitle)]} { |
||||
set opts(balloontitle) [string range $opts(balloontitle) 0 63] |
||||
} else { |
||||
set opts(balloontitle) "" |
||||
} |
||||
|
||||
# Calculate padding for text fields (in bytes so 2*num padchars) |
||||
set tip_padcount [expr {2*(128 - [string length $opts(tip)])}] |
||||
set balloon_padcount [expr {2*(256 - [string length $opts(balloon)])}] |
||||
set balloontitle_padcount [expr {2 * (64 - [string length $opts(balloontitle)])}] |
||||
if {$opts(balloonicon) eq "user"} { |
||||
if {![min_os_version 5 1 2]} { |
||||
# 'user' not supported before XP SP2 |
||||
set opts(balloonicon) none |
||||
} |
||||
} |
||||
|
||||
set balloonflags [dict get { |
||||
none 0 |
||||
info 1 |
||||
warning 2 |
||||
error 3 |
||||
user 4 |
||||
} $opts(balloonicon)] |
||||
|
||||
if {$balloonflags == 4} { |
||||
if {![info exists opts(hicon)]} { |
||||
error "Option -hicon must be specified if value of -balloonicon option is 'user'" |
||||
} |
||||
} |
||||
|
||||
if {$opts(silent)} { |
||||
incr balloonflags 0x10 |
||||
} |
||||
|
||||
if {$::tcl_platform(pointerSize) == 8} { |
||||
set addrfmt m |
||||
set alignment x4 |
||||
} else { |
||||
set addrfmt n |
||||
set alignment x0 |
||||
} |
||||
|
||||
set hwnd [pointer_to_address [Twapi_GetNotificationWindow]] |
||||
set opts(hicon) [pointer_to_address $opts(hicon)] |
||||
|
||||
set bin [binary format "${alignment}${addrfmt}nnn" $hwnd $id $flags [_get_script_wm NOTIFY_ICON_CALLBACK]] |
||||
append bin \ |
||||
[binary format ${alignment}${addrfmt} $opts(hicon)] \ |
||||
[encoding convertto unicode $opts(tip)] \ |
||||
[binary format "x${tip_padcount}nn" $state $statemask] \ |
||||
[encoding convertto unicode $opts(balloon)] \ |
||||
[binary format "x${balloon_padcount}n" $timeout_or_version] \ |
||||
[encoding convertto unicode $opts(balloontitle)] \ |
||||
[binary format "x${balloontitle_padcount}nx16" $balloonflags] |
||||
return "[binary format n [expr {4+[string length $bin]}]]$bin" |
||||
} |
||||
|
||||
proc addicon {hicon {cmdprefix ""}} { |
||||
variable _icon_id_ctr |
||||
variable _icondata |
||||
|
||||
_register_script_wm_handler [_get_script_wm NOTIFY_ICON_CALLBACK] [list [namespace current]::_icon_handler] 1 |
||||
_register_script_wm_handler [_get_script_wm TASKBAR_RESTART] [list [namespace current]::_taskbar_restart_handler] 1 |
||||
|
||||
set id [incr _icon_id_ctr] |
||||
|
||||
# 0 -> Add |
||||
Shell_NotifyIcon 0 [_make_NOTIFYICONW $id -hicon $hicon] |
||||
|
||||
# 4 -> set version (controls notification behaviour) to 3 (Win2K+) |
||||
if {[catch { |
||||
Shell_NotifyIcon 4 [_make_NOTIFYICONW $id -version 3] |
||||
} ermsg]} { |
||||
set ercode $::errorCode |
||||
set erinfo $::errorInfo |
||||
removeicon $id |
||||
error $ermsg $erinfo $ercode |
||||
} |
||||
|
||||
if {[llength $cmdprefix]} { |
||||
dict set _icondata $id handler $cmdprefix |
||||
} |
||||
dict set _icondata $id hicon $hicon |
||||
|
||||
return $id |
||||
} |
||||
|
||||
proc removeicon {id} { |
||||
variable _icondata |
||||
|
||||
# Ignore errors in case dup call |
||||
catch {Shell_NotifyIcon 2 [_make_NOTIFYICONW $id]} |
||||
dict unset _icondata $id |
||||
} |
||||
|
||||
proc modifyicon {id args} { |
||||
# TBD - do we need to [dict set _icondata hicon ...] ? |
||||
Shell_NotifyIcon 1 [_make_NOTIFYICONW $id {*}$args] |
||||
} |
||||
|
||||
proc _icon_handler {msg id notification msgpos ticks} { |
||||
variable _icondata |
||||
variable _message_map |
||||
|
||||
if {![dict exists $_icondata $id handler]} { |
||||
return; # Stale or no handler specified |
||||
} |
||||
|
||||
# Translate the notification into text |
||||
if {[info exists _message_map($notification)]} { |
||||
set notification $_message_map($notification) |
||||
} |
||||
|
||||
uplevel #0 [linsert [dict get $_icondata $id handler] end $id $notification $msgpos $ticks] |
||||
} |
||||
|
||||
proc _taskbar_restart_handler {args} { |
||||
variable _icondata |
||||
# Need to add icons back into taskbar |
||||
dict for {id icodata} $_icondata { |
||||
# 0 -> Add |
||||
Shell_NotifyIcon 0 [_make_NOTIFYICONW $id -hicon [dict get $icodata hicon]] |
||||
} |
||||
} |
||||
|
||||
namespace export addicon modifyicon removeicon |
||||
namespace ensemble create |
||||
} |
@ -0,0 +1,801 @@
|
||||
# |
||||
# Copyright (c) 2007-2013, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
namespace eval twapi { |
||||
|
||||
|
||||
# Holds SSPI security contexts indexed by a handle |
||||
# Each element is a dict with the following keys: |
||||
# State - state of the security context - see sspi_step |
||||
# Handle - the Win32 SecHandle for the context |
||||
# Input - Pending input from remote end to be passed in to |
||||
# SSPI provider (only valid for streams) |
||||
# Output - list of SecBuffers that contain data to be sent |
||||
# to remote end during a SSPI negotiation |
||||
# Inattr - requested context attributes |
||||
# Outattr - context attributes returned from service provider |
||||
# (currently not used) |
||||
# Expiration - time when context will expire |
||||
# Ctxtype - client, server |
||||
# Target - |
||||
# Datarep - data representation format |
||||
# Credentials - handle for credentials to pass to sspi provider |
||||
variable _sspi_state |
||||
array set _sspi_state {} |
||||
|
||||
proc* _init_security_context_syms {} { |
||||
variable _server_security_context_syms |
||||
variable _client_security_context_syms |
||||
variable _secpkg_capability_syms |
||||
|
||||
|
||||
# Symbols used for mapping server security context flags |
||||
array set _server_security_context_syms { |
||||
confidentiality 0x10 |
||||
connection 0x800 |
||||
delegate 0x1 |
||||
extendederror 0x8000 |
||||
identify 0x80000 |
||||
integrity 0x20000 |
||||
mutualauth 0x2 |
||||
replaydetect 0x4 |
||||
sequencedetect 0x8 |
||||
stream 0x10000 |
||||
} |
||||
|
||||
# Symbols used for mapping client security context flags |
||||
array set _client_security_context_syms { |
||||
confidentiality 0x10 |
||||
connection 0x800 |
||||
delegate 0x1 |
||||
extendederror 0x4000 |
||||
identify 0x20000 |
||||
integrity 0x10000 |
||||
manualvalidation 0x80000 |
||||
mutualauth 0x2 |
||||
replaydetect 0x4 |
||||
sequencedetect 0x8 |
||||
stream 0x8000 |
||||
usesessionkey 0x20 |
||||
usesuppliedcreds 0x80 |
||||
} |
||||
|
||||
# Symbols used for mapping security package capabilities |
||||
array set _secpkg_capability_syms { |
||||
integrity 0x00000001 |
||||
privacy 0x00000002 |
||||
tokenonly 0x00000004 |
||||
datagram 0x00000008 |
||||
connection 0x00000010 |
||||
multirequired 0x00000020 |
||||
clientonly 0x00000040 |
||||
extendederror 0x00000080 |
||||
impersonation 0x00000100 |
||||
acceptwin32name 0x00000200 |
||||
stream 0x00000400 |
||||
negotiable 0x00000800 |
||||
gsscompatible 0x00001000 |
||||
logon 0x00002000 |
||||
asciibuffers 0x00004000 |
||||
fragment 0x00008000 |
||||
mutualauth 0x00010000 |
||||
delegation 0x00020000 |
||||
readonlywithchecksum 0x00040000 |
||||
restrictedtokens 0x00080000 |
||||
negoextender 0x00100000 |
||||
negotiable2 0x00200000 |
||||
appcontainerpassthrough 0x00400000 |
||||
appcontainerchecks 0x00800000 |
||||
} |
||||
} {} |
||||
} |
||||
|
||||
# Return list of security packages |
||||
proc twapi::sspi_enumerate_packages {args} { |
||||
set pkgs [EnumerateSecurityPackages] |
||||
if {[llength $args] == 0} { |
||||
set names [list ] |
||||
foreach pkg $pkgs { |
||||
lappend names [kl_get $pkg Name] |
||||
} |
||||
return $names |
||||
} |
||||
|
||||
# TBD - why is this hyphenated ? |
||||
array set opts [parseargs args { |
||||
all capabilities version rpcid maxtokensize name comment |
||||
} -maxleftover 0 -hyphenated] |
||||
|
||||
_init_security_context_syms |
||||
variable _secpkg_capability_syms |
||||
set retdata {} |
||||
foreach pkg $pkgs { |
||||
set rec {} |
||||
if {$opts(-all) || $opts(-capabilities)} { |
||||
lappend rec -capabilities [_make_symbolic_bitmask [kl_get $pkg fCapabilities] _secpkg_capability_syms] |
||||
} |
||||
foreach {opt field} {-version wVersion -rpcid wRPCID -maxtokensize cbMaxToken -name Name -comment Comment} { |
||||
if {$opts(-all) || $opts($opt)} { |
||||
lappend rec $opt [kl_get $pkg $field] |
||||
} |
||||
} |
||||
dict set recdata [kl_get $pkg Name] $rec |
||||
} |
||||
return $recdata |
||||
} |
||||
|
||||
proc twapi::sspi_schannel_credentials args { |
||||
# TBD - do all these options work ? Check before documenting |
||||
# since they seem to be duplicated in InitializeSecurityContext |
||||
parseargs args { |
||||
certificates.arg |
||||
{rootstore.arg NULL} |
||||
sessionlifespan.int |
||||
usedefaultclientcert.bool |
||||
{disablereconnects.bool 0 0x80} |
||||
{revocationcheck.arg none {full endonly excluderoot none}} |
||||
{ignoreerrorrevocationoffline.bool 0 0x1000} |
||||
{ignoreerrornorevocationcheck.bool 0 0x800} |
||||
{validateservercert.bool 1} |
||||
cipherstrength.arg |
||||
protocols.arg |
||||
} -setvars -nulldefault -maxleftover 0 |
||||
|
||||
set flags [expr {$disablereconnects | $ignoreerrornorevocationcheck | $ignoreerrorrevocationoffline}] |
||||
incr flags [dict get { |
||||
none 0 full 0x200 excluderoot 0x400 endonly 0x100 |
||||
} $revocationcheck] |
||||
|
||||
if {$validateservercert} { |
||||
incr flags 0x20; # SCH_CRED_AUTO_CRED_VALIDATION |
||||
} else { |
||||
incr flags 0x8; # SCH_CRED_MANUAL_CRED_VALIDATION |
||||
} |
||||
if {$usedefaultclientcert} { |
||||
incr flags 0x40; # SCH_CRED_USE_DEFAULT_CREDS |
||||
} else { |
||||
incr flags 0x10; # SCH_CRED_NO_DEFAULT_CREDS |
||||
} |
||||
|
||||
set protbits 0 |
||||
foreach prot $protocols { |
||||
set protbits [expr { |
||||
$protbits | [dict! { |
||||
ssl2 0xc ssl3 0x30 tls1 0xc0 tls1.1 0x300 tls1.2 0xc00 |
||||
} $prot] |
||||
}] |
||||
} |
||||
|
||||
switch [llength $cipherstrength] { |
||||
0 { set minbits 0 ; set maxbits 0 } |
||||
1 { set minbits [lindex $cipherstrength 0] ; set maxbits $minbits } |
||||
2 { |
||||
set minbits [lindex $cipherstrength 0] |
||||
set maxbits [lindex $cipherstrength 1] |
||||
} |
||||
default { |
||||
error "Invalid value '$cipherstrength' for option -cipherstrength" |
||||
} |
||||
} |
||||
|
||||
# 4 -> SCHANNEL_CRED_VERSION |
||||
return [list 4 $certificates $rootstore {} {} $protbits $minbits $maxbits $sessionlifespan $flags 0] |
||||
} |
||||
|
||||
proc twapi::sspi_winnt_identity_credentials {user domain password} { |
||||
return [list $user $domain $password] |
||||
} |
||||
|
||||
proc twapi::sspi_acquire_credentials {args} { |
||||
parseargs args { |
||||
{credentials.arg {}} |
||||
principal.arg |
||||
{package.arg NTLM} |
||||
{role.arg both {client server inbound outbound both}} |
||||
getexpiration |
||||
} -maxleftover 0 -setvars -nulldefault |
||||
|
||||
set creds [AcquireCredentialsHandle $principal \ |
||||
[dict* { |
||||
unisp {Microsoft Unified Security Protocol Provider} |
||||
ssl {Microsoft Unified Security Protocol Provider} |
||||
tls {Microsoft Unified Security Protocol Provider} |
||||
} $package] \ |
||||
[kl_get {inbound 1 server 1 outbound 2 client 2 both 3} $role] \ |
||||
"" $credentials] |
||||
|
||||
if {$getexpiration} { |
||||
return [kl_create2 {-handle -expiration} $creds] |
||||
} else { |
||||
return [lindex $creds 0] |
||||
} |
||||
} |
||||
|
||||
# Frees credentials |
||||
proc twapi::sspi_free_credentials {cred} { |
||||
FreeCredentialsHandle $cred |
||||
} |
||||
|
||||
# Return a client context |
||||
proc twapi::sspi_client_context {cred args} { |
||||
_init_security_context_syms |
||||
variable _client_security_context_syms |
||||
|
||||
parseargs args { |
||||
target.arg |
||||
{datarep.arg network {native network}} |
||||
confidentiality.bool |
||||
connection.bool |
||||
delegate.bool |
||||
extendederror.bool |
||||
identify.bool |
||||
integrity.bool |
||||
manualvalidation.bool |
||||
mutualauth.bool |
||||
replaydetect.bool |
||||
sequencedetect.bool |
||||
stream.bool |
||||
usesessionkey.bool |
||||
usesuppliedcreds.bool |
||||
} -maxleftover 0 -nulldefault -setvars |
||||
|
||||
set context_flags 0 |
||||
foreach {opt flag} [array get _client_security_context_syms] { |
||||
if {[set $opt]} { |
||||
set context_flags [expr {$context_flags | $flag}] |
||||
} |
||||
} |
||||
|
||||
set drep [kl_get {native 0x10 network 0} $datarep] |
||||
return [_construct_sspi_security_context \ |
||||
sspiclient#[TwapiId] \ |
||||
[InitializeSecurityContext \ |
||||
$cred \ |
||||
"" \ |
||||
$target \ |
||||
$context_flags \ |
||||
0 \ |
||||
$drep \ |
||||
[list ] \ |
||||
0] \ |
||||
client \ |
||||
$context_flags \ |
||||
$target \ |
||||
$cred \ |
||||
$drep \ |
||||
] |
||||
} |
||||
|
||||
# Delete a security context |
||||
proc twapi::sspi_delete_context {ctx} { |
||||
variable _sspi_state |
||||
set h [_sspi_context_handle $ctx] |
||||
if {[llength $h]} { |
||||
DeleteSecurityContext $h |
||||
} |
||||
unset _sspi_state($ctx) |
||||
} |
||||
|
||||
# Shuts down a security context in orderly fashion |
||||
# Caller should start sspi_step |
||||
proc twapi::sspi_shutdown_context {ctx} { |
||||
variable _sspi_state |
||||
|
||||
_sspi_context_handle $ctx; # Verify handle |
||||
dict with _sspi_state($ctx) { |
||||
switch -nocase -- [lindex [QueryContextAttributes $Handle 10] 4] { |
||||
schannel - |
||||
"Microsoft Unified Security Protocol Provider" {} |
||||
default { return } |
||||
} |
||||
|
||||
# Signal to security provider we want to shutdown |
||||
Twapi_ApplyControlToken_SCHANNEL_SHUTDOWN $Handle |
||||
|
||||
if {$Ctxtype eq "client"} { |
||||
set rawctx [InitializeSecurityContext \ |
||||
$Credentials \ |
||||
$Handle \ |
||||
$Target \ |
||||
$Inattr \ |
||||
0 \ |
||||
$Datarep \ |
||||
[list ] \ |
||||
0] |
||||
} else { |
||||
set rawctx [AcceptSecurityContext \ |
||||
$Credentials \ |
||||
$Handle \ |
||||
[list ] \ |
||||
$Inattr \ |
||||
$Datarep] |
||||
} |
||||
lassign $rawctx State Handle out Outattr Expiration extra |
||||
if {$State in {ok expired}} { |
||||
return [list done [_gather_secbuf_data $out]] |
||||
} else { |
||||
return [list continue [_gather_secbuf_data $out]] |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Take the next step in an SSPI negotiation |
||||
# Returns |
||||
# {done data extradata} |
||||
# {continue data} |
||||
# {expired data} |
||||
proc twapi::sspi_step {ctx {received ""}} { |
||||
variable _sspi_state |
||||
variable _client_security_context_syms |
||||
|
||||
_sspi_validate_handle $ctx |
||||
|
||||
dict with _sspi_state($ctx) { |
||||
# Note the dictionary content variables are |
||||
# State, Handle, Output, Outattr, Expiration, |
||||
# Ctxtype, Inattr, Target, Datarep, Credentials |
||||
|
||||
# Append new input to existing input |
||||
append Input $received |
||||
switch -exact -- $State { |
||||
ok { |
||||
set data [_gather_secbuf_data $Output] |
||||
set Output {} |
||||
|
||||
# $Input at this point contains left over input that is |
||||
# actually application data (streaming case). |
||||
# Application should pass this to decrypt commands |
||||
return [list done $data $Input[set Input ""]] |
||||
} |
||||
continue { |
||||
# Continue with the negotiation |
||||
if {[string length $Input] != 0} { |
||||
# Pass in received data to SSPI. |
||||
# Most providers take only the first buffer |
||||
# but SChannel/UNISP need the second. Since |
||||
# others don't seem to mind the second buffer |
||||
# we always always include it |
||||
# 2 -> SECBUFFER_TOKEN, 0 -> SECBUFFER_EMPTY |
||||
set inbuflist [list [list 2 $Input] [list 0]] |
||||
if {$Ctxtype eq "client"} { |
||||
set rawctx [InitializeSecurityContext \ |
||||
$Credentials \ |
||||
$Handle \ |
||||
$Target \ |
||||
$Inattr \ |
||||
0 \ |
||||
$Datarep \ |
||||
$inbuflist \ |
||||
0] |
||||
} else { |
||||
set rawctx [AcceptSecurityContext \ |
||||
$Credentials \ |
||||
$Handle \ |
||||
$inbuflist \ |
||||
$Inattr \ |
||||
$Datarep] |
||||
} |
||||
lassign $rawctx State Handle out Outattr Expiration extra |
||||
lappend Output {*}$out |
||||
# When the error is incomplete_credentials, we will retry |
||||
# with the SEC_I_INCOMPLETE_CREDENTIALS flag set. For |
||||
# this the Input should remain the same. Otherwise set it |
||||
# to whatever remains to be processed in the buffer. |
||||
if {$State ne "incomplete_credentials"} { |
||||
set Input $extra |
||||
} |
||||
# Will recurse at proc end |
||||
} else { |
||||
# There was no received data. Return any data |
||||
# to be sent to remote end |
||||
set data [_gather_secbuf_data $Output] |
||||
set Output {} |
||||
return [list continue $data ""] |
||||
} |
||||
} |
||||
incomplete_message { |
||||
# Caller has to get more data from remote end |
||||
set State continue |
||||
return [list continue "" ""] |
||||
} |
||||
expired { |
||||
# Remote end closed in middle of negotiation |
||||
return [list disconnected "" ""] |
||||
} |
||||
incomplete_credentials { |
||||
# In this state, the remote has asked for an client certificate. |
||||
# In this case, we ask Schannel to limit itself to whatever |
||||
# the user supplied and retry. Servers that ask for a cert |
||||
# but do not mandate it will then proceed. However, we only |
||||
# do this if we have not already tried this route. If we have, |
||||
# then generate an error. The real solution would be to attempt |
||||
# to look up new credentials by retrieving a certificate |
||||
# from the certificate store (possibly by asking the user) but |
||||
# this is not implemented. |
||||
# TBD - get client cert from user. See |
||||
# https://github.com/david-maw/StreamSSL and |
||||
# https://www.codeproject.com/Articles/1094525/Configuring-SSL-and-Client-Certificate-Validation |
||||
if {$Inattr & $_client_security_context_syms(usesuppliedcreds)} { |
||||
# Already tried with this. Give up. |
||||
set ermsg "Handling of incomplete credentials not implemented. If using TLS, specify the -credentials option to tls_socket to provide credentials." |
||||
error $ermsg "" [list TWAPI SSPI UNSUPPORTED $ermsg] |
||||
} |
||||
set Inattr [expr {$Inattr | $_client_security_context_syms(usesuppliedcreds)}] |
||||
set State continue |
||||
# Fall to bottom to recurse one more time |
||||
} |
||||
complete - |
||||
complete_and_continue { |
||||
# Should not actually occur as sspi.c no longer returns |
||||
# these codes |
||||
error "State $State handling not implemented." |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Recurse to return next state. |
||||
# This has to be OUTSIDE the [dict with] above else it will not |
||||
# see the updated values |
||||
return [sspi_step $ctx] |
||||
} |
||||
|
||||
# Return a server context |
||||
proc twapi::sspi_server_context {cred clientdata args} { |
||||
_init_security_context_syms |
||||
variable _server_security_context_syms |
||||
|
||||
parseargs args { |
||||
{datarep.arg network {native network}} |
||||
confidentiality.bool |
||||
connection.bool |
||||
delegate.bool |
||||
extendederror.bool |
||||
identify.bool |
||||
integrity.bool |
||||
mutualauth.bool |
||||
replaydetect.bool |
||||
sequencedetect.bool |
||||
stream.bool |
||||
} -maxleftover 0 -nulldefault -setvars |
||||
|
||||
set context_flags 0 |
||||
foreach {opt flag} [array get _server_security_context_syms] { |
||||
if {[set $opt]} { |
||||
set context_flags [expr {$context_flags | $flag}] |
||||
} |
||||
} |
||||
|
||||
set drep [kl_get {native 0x10 network 0} $datarep] |
||||
return [_construct_sspi_security_context \ |
||||
sspiserver#[TwapiId] \ |
||||
[AcceptSecurityContext \ |
||||
$cred \ |
||||
"" \ |
||||
[list [list 2 $clientdata]] \ |
||||
$context_flags \ |
||||
$drep] \ |
||||
server \ |
||||
$context_flags \ |
||||
"" \ |
||||
$cred \ |
||||
$drep \ |
||||
] |
||||
} |
||||
|
||||
|
||||
# Get the security context flags after completion of request |
||||
proc ::twapi::sspi_context_features {ctx} { |
||||
variable _sspi_state |
||||
|
||||
set ctxh [_sspi_context_handle $ctx] |
||||
|
||||
_init_security_context_syms |
||||
|
||||
# We could directly look in the context itself but intead we make |
||||
# an explicit call, just in case they change after initial setup |
||||
set flags [QueryContextAttributes $ctxh 14] |
||||
|
||||
# Mapping of symbols depends on whether it is a client or server |
||||
# context |
||||
if {[dict get $_sspi_state($ctx) Ctxtype] eq "client"} { |
||||
upvar 0 [namespace current]::_client_security_context_syms syms |
||||
} else { |
||||
upvar 0 [namespace current]::_server_security_context_syms syms |
||||
} |
||||
|
||||
set result [list -raw $flags] |
||||
foreach {sym flag} [array get syms] { |
||||
lappend result -$sym [expr {($flag & $flags) != 0}] |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
# Get the user name for a security context |
||||
proc twapi::sspi_context_username {ctx} { |
||||
return [QueryContextAttributes [_sspi_context_handle $ctx] 1] |
||||
} |
||||
|
||||
# Get the field size information for a security context |
||||
# TBD - update for SSL |
||||
proc twapi::sspi_context_sizes {ctx} { |
||||
set sizes [QueryContextAttributes [_sspi_context_handle $ctx] 0] |
||||
return [twine {-maxtoken -maxsig -blocksize -trailersize} $sizes] |
||||
} |
||||
|
||||
proc twapi::sspi_remote_cert {ctx} { |
||||
return [QueryContextAttributes [_sspi_context_handle $ctx] 0x53] |
||||
} |
||||
|
||||
proc twapi::sspi_local_cert {ctx} { |
||||
return [QueryContextAttributes [_sspi_context_handle $ctx] 0x54] |
||||
} |
||||
|
||||
proc twapi::sspi_issuers_accepted_by_peer {ctx} { |
||||
return [QueryContextAttributes [_sspi_context_handle $ctx] 0x59] |
||||
} |
||||
|
||||
# Returns a signature |
||||
proc twapi::sspi_sign {ctx data args} { |
||||
parseargs args { |
||||
{seqnum.int 0} |
||||
{qop.int 0} |
||||
} -maxleftover 0 -setvars |
||||
|
||||
return [MakeSignature \ |
||||
[_sspi_context_handle $ctx] \ |
||||
$qop \ |
||||
$data \ |
||||
$seqnum] |
||||
} |
||||
|
||||
# Verify signature |
||||
proc twapi::sspi_verify_signature {ctx sig data args} { |
||||
parseargs args { |
||||
{seqnum.int 0} |
||||
} -maxleftover 0 -setvars |
||||
|
||||
# Buffer type 2 - Token, 1- Data |
||||
return [VerifySignature \ |
||||
[_sspi_context_handle $ctx] \ |
||||
[list [list 2 $sig] [list 1 $data]] \ |
||||
$seqnum] |
||||
} |
||||
|
||||
# Encrypts a data as per a context |
||||
# Returns {securitytrailer encrypteddata padding} |
||||
proc twapi::sspi_encrypt {ctx data args} { |
||||
parseargs args { |
||||
{seqnum.int 0} |
||||
{qop.int 0} |
||||
} -maxleftover 0 -setvars |
||||
|
||||
return [EncryptMessage \ |
||||
[_sspi_context_handle $ctx] \ |
||||
$qop \ |
||||
$data \ |
||||
$seqnum] |
||||
} |
||||
|
||||
proc twapi::sspi_encrypt_stream {ctx data args} { |
||||
variable _sspi_state |
||||
|
||||
set h [_sspi_context_handle $ctx] |
||||
|
||||
# TBD - docment options |
||||
parseargs args { |
||||
{qop.int 0} |
||||
} -maxleftover 0 -setvars |
||||
|
||||
set enc "" |
||||
while {[string length $data]} { |
||||
lassign [EncryptStream $h $qop $data] fragment data |
||||
lappend enc $fragment |
||||
} |
||||
|
||||
return [join $enc ""] |
||||
} |
||||
|
||||
# chan must be in binary mode |
||||
proc twapi::sspi_encrypt_and_write {ctx data chan args} { |
||||
variable _sspi_state |
||||
|
||||
set h [_sspi_context_handle $ctx] |
||||
|
||||
parseargs args { |
||||
{qop.int 0} |
||||
{flush.bool 1} |
||||
} -maxleftover 0 -setvars |
||||
|
||||
while {[string length $data]} { |
||||
lassign [EncryptStream $h $qop $data] fragment data |
||||
puts -nonewline $chan $fragment |
||||
} |
||||
|
||||
if {$flush} { |
||||
chan flush $chan |
||||
} |
||||
} |
||||
|
||||
|
||||
# Decrypts a message |
||||
# TBD - why does this not return a status like sspi_decrypt_stream ? |
||||
proc twapi::sspi_decrypt {ctx sig data padding args} { |
||||
variable _sspi_state |
||||
_sspi_validate_handle $ctx |
||||
|
||||
parseargs args { |
||||
{seqnum.int 0} |
||||
} -maxleftover 0 -setvars |
||||
|
||||
# Buffer type 2 - Token, 1- Data, 9 - padding |
||||
set decrypted [DecryptMessage \ |
||||
[dict get $_sspi_state($ctx) Handle] \ |
||||
[list [list 2 $sig] [list 1 $data] [list 9 $padding]] \ |
||||
$seqnum] |
||||
set plaintext {} |
||||
# Pick out only the data buffers, ignoring pad buffers and signature |
||||
# Optimize copies by keeping as a list so in the common case of a |
||||
# single buffer can return it as is. Multiple buffers are expensive |
||||
# because Tcl will shimmer each byte array into a list and then |
||||
# incur additional copies during joining |
||||
foreach buf $decrypted { |
||||
# SECBUFFER_DATA -> 1 |
||||
if {[lindex $buf 0] == 1} { |
||||
lappend plaintext [lindex $buf 1] |
||||
} |
||||
} |
||||
|
||||
if {[llength $plaintext] < 2} { |
||||
return [lindex $plaintext 0] |
||||
} else { |
||||
return [join $plaintext ""] |
||||
} |
||||
} |
||||
|
||||
# Decrypts a stream |
||||
proc twapi::sspi_decrypt_stream {ctx data} { |
||||
variable _sspi_state |
||||
set hctx [_sspi_context_handle $ctx] |
||||
|
||||
# SSL decryption is done in max size chunks. |
||||
# We will loop collecting as much data as possible. Collecting |
||||
# as a list and joining at end minimizes internal byte copies |
||||
set plaintext {} |
||||
lassign [DecryptStream $hctx [dict get $_sspi_state($ctx) Input] $data] status decrypted extra |
||||
lappend plaintext $decrypted |
||||
|
||||
# TBD - handle renegotiate status |
||||
while {$status eq "ok" && [string length $extra]} { |
||||
# See if additional data and loop again |
||||
lassign [DecryptStream $hctx $extra] status decrypted extra |
||||
lappend plaintext $decrypted |
||||
} |
||||
|
||||
dict set _sspi_state($ctx) Input $extra |
||||
if {$status eq "incomplete_message"} { |
||||
set status ok |
||||
} |
||||
return [list $status [join $plaintext ""]] |
||||
} |
||||
|
||||
|
||||
################################################################ |
||||
# Utility procs |
||||
|
||||
|
||||
# Construct a high level SSPI security context structure |
||||
# rawctx is context as returned from C level code |
||||
proc twapi::_construct_sspi_security_context {id rawctx ctxtype inattr target credentials datarep} { |
||||
variable _sspi_state |
||||
|
||||
set _sspi_state($id) [dict merge [dict create Ctxtype $ctxtype \ |
||||
Inattr $inattr \ |
||||
Target $target \ |
||||
Datarep $datarep \ |
||||
Credentials $credentials] \ |
||||
[twine \ |
||||
{State Handle Output Outattr Expiration Input} \ |
||||
$rawctx]] |
||||
|
||||
return $id |
||||
} |
||||
|
||||
proc twapi::_sspi_validate_handle {ctx} { |
||||
variable _sspi_state |
||||
|
||||
if {![info exists _sspi_state($ctx)]} { |
||||
badargs! "Invalid SSPI security context handle $ctx" 3 |
||||
} |
||||
} |
||||
|
||||
proc twapi::_sspi_context_handle {ctx} { |
||||
variable _sspi_state |
||||
|
||||
if {![info exists _sspi_state($ctx)]} { |
||||
badargs! "Invalid SSPI security context handle $ctx" 3 |
||||
} |
||||
|
||||
return [dict get $_sspi_state($ctx) Handle] |
||||
} |
||||
|
||||
proc twapi::_gather_secbuf_data {bufs} { |
||||
if {[llength $bufs] == 1} { |
||||
return [lindex [lindex $bufs 0] 1] |
||||
} else { |
||||
set data {} |
||||
foreach buf $bufs { |
||||
# First element is buffer type, which we do not care |
||||
# Second element is actual data |
||||
lappend data [lindex $buf 1] |
||||
} |
||||
return [join $data {}] |
||||
} |
||||
} |
||||
|
||||
if {0} { |
||||
TBD - delete |
||||
set cred [sspi_acquire_credentials -package ssl -role client] |
||||
set client [sspi_client_context $cred -stream 1 -manualvalidation 1] |
||||
set out [sspi_step $client] |
||||
set so [socket 192.168.1.127 443] |
||||
fconfigure $so -blocking 0 -buffering none -translation binary |
||||
puts -nonewline $so [lindex $out 1] |
||||
|
||||
set data [read $so] |
||||
set out [sspi_step $client $data] |
||||
puts -nonewline $so [lindex $out 1] |
||||
|
||||
set data [read $so] |
||||
set out [sspi_step $client $data] |
||||
|
||||
set out [sspi_encrypt_stream $client "GET / HTTP/1.0\r\n\r\n"] |
||||
puts -nonewline $so $out |
||||
set data [read $so] |
||||
set d [sspi_decrypt_stream $client $data] |
||||
sspi_shutdown_context $client |
||||
close $so ; sspi_free_credentials $cred ; sspi_free_context $client |
||||
sspi_context_free $client |
||||
sspi_shutdown_context $client |
||||
|
||||
# INTERNAL client-server |
||||
proc 'sslsetup {} { |
||||
uplevel #0 { |
||||
twapi |
||||
source ../tests/testutil.tcl |
||||
set ca [make_test_certs] |
||||
set cacert [cert_store_find_certificate $ca subject_substring twapitestca] |
||||
set scert [cert_store_find_certificate $ca subject_substring twapitestserver] |
||||
set scred [sspi_acquire_credentials -package ssl -role server -credentials [sspi_schannel_credentials -certificates [list $scert]]] |
||||
set ccert [cert_store_find_certificate $ca subject_substring twapitestclient] |
||||
set ccred [sspi_acquire_credentials -package ssl -role client -credentials [sspi_schannel_credentials]] |
||||
set cctx [sspi_client_context $ccred -stream 1 -manualvalidation 1] |
||||
set cstep [sspi_step $cctx] |
||||
|
||||
set sctx [sspi_server_context $scred [lindex $cstep 1] -stream 1] |
||||
set sstep [sspi_step $sctx] |
||||
set cstep [sspi_step $cctx [lindex $sstep 1]] |
||||
set sstep [sspi_step $sctx [lindex $cstep 1]] |
||||
set cstep [sspi_step $cctx [lindex $sstep 1]] |
||||
} |
||||
} |
||||
set out [sspi_encrypt_stream $cctx "This is a test"] |
||||
|
||||
sspi_decrypt_stream $sctx $out |
||||
sspi_decrypt_stream $sctx "" |
||||
set out [sspi_encrypt_stream $sctx "This is a testx"] |
||||
sspi_decrypt_stream $cctx $out |
||||
|
||||
proc 'ccred {} { |
||||
set store [cert_system_store_open twapitest user] |
||||
set ccert [cert_store_find_certificate $store subject_substring twapitestclient] |
||||
set ccred [sspi_acquire_credentials -package ssl -role client -credentials [sspi_schannel_credentials -certificates [list $ccert]]] |
||||
cert_store_release $store |
||||
cert_release $ccert |
||||
return $ccred |
||||
} |
||||
|
||||
} |
@ -0,0 +1,616 @@
|
||||
# |
||||
# Copyright (c) 2003, 2008 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# TBD - convert file spec to drive root path |
||||
|
||||
# Get info associated with a drive |
||||
proc twapi::get_volume_info {drive args} { |
||||
|
||||
set drive [_drive_rootpath $drive] |
||||
|
||||
array set opts [parseargs args { |
||||
all size freespace used useravail type serialnum label maxcomponentlen fstype attr device extents |
||||
} -maxleftover 0] |
||||
|
||||
if {$opts(all)} { |
||||
# -all option does not cover -type, -extents and -device |
||||
foreach opt { |
||||
all size freespace used useravail serialnum label maxcomponentlen fstype attr |
||||
} { |
||||
set opts($opt) 1 |
||||
} |
||||
} |
||||
|
||||
set result [list ] |
||||
if {$opts(size) || $opts(freespace) || $opts(used) || $opts(useravail)} { |
||||
lassign [GetDiskFreeSpaceEx $drive] useravail size freespace |
||||
foreach opt {size freespace useravail} { |
||||
if {$opts($opt)} { |
||||
lappend result -$opt [set $opt] |
||||
} |
||||
} |
||||
if {$opts(used)} { |
||||
lappend result -used [expr {$size - $freespace}] |
||||
} |
||||
} |
||||
|
||||
if {$opts(type)} { |
||||
set drive_type [get_drive_type $drive] |
||||
lappend result -type $drive_type |
||||
} |
||||
if {$opts(device)} { |
||||
if {[_is_unc $drive]} { |
||||
# UNC paths cannot be used with QueryDosDevice |
||||
lappend result -device "" |
||||
} else { |
||||
lappend result -device [QueryDosDevice [string range $drive 0 1]] |
||||
} |
||||
} |
||||
|
||||
if {$opts(extents)} { |
||||
set extents {} |
||||
if {! [_is_unc $drive]} { |
||||
trap { |
||||
set device_handle [create_file "\\\\.\\[string range $drive 0 1]" -createdisposition open_existing] |
||||
set bin [device_ioctl $device_handle 0x560000 -outputcount 32] |
||||
if {[binary scan $bin i nextents] != 1} { |
||||
error "Truncated information returned from ioctl 0x560000" |
||||
} |
||||
set off 8 |
||||
for {set i 0} {$i < $nextents} {incr i} { |
||||
if {[binary scan $bin "@$off i x4 w w" extent(-disknumber) extent(-startingoffset) extent(-extentlength)] != 3} { |
||||
error "Truncated information returned from ioctl 0x560000" |
||||
} |
||||
lappend extents [array get extent] |
||||
incr off 24; # Size of one extent element |
||||
} |
||||
} onerror {} { |
||||
# Do nothing, device does not support extents or access denied |
||||
# Empty list is returned |
||||
} finally { |
||||
if {[info exists device_handle]} { |
||||
CloseHandle $device_handle |
||||
} |
||||
} |
||||
} |
||||
|
||||
lappend result -extents $extents |
||||
} |
||||
|
||||
if {$opts(serialnum) || $opts(label) || $opts(maxcomponentlen) |
||||
|| $opts(fstype) || $opts(attr)} { |
||||
foreach {label serialnum maxcomponentlen attr fstype} \ |
||||
[GetVolumeInformation $drive] { break } |
||||
foreach opt {label maxcomponentlen fstype} { |
||||
if {$opts($opt)} { |
||||
lappend result -$opt [set $opt] |
||||
} |
||||
} |
||||
if {$opts(serialnum)} { |
||||
set low [expr {$serialnum & 0x0000ffff}] |
||||
set high [expr {($serialnum >> 16) & 0x0000ffff}] |
||||
lappend result -serialnum [format "%.4X-%.4X" $high $low] |
||||
} |
||||
if {$opts(attr)} { |
||||
set attrs [list ] |
||||
foreach {sym val} { |
||||
case_preserved_names 2 |
||||
unicode_on_disk 4 |
||||
persistent_acls 8 |
||||
file_compression 16 |
||||
volume_quotas 32 |
||||
supports_sparse_files 64 |
||||
supports_reparse_points 128 |
||||
supports_remote_storage 256 |
||||
volume_is_compressed 0x8000 |
||||
supports_object_ids 0x10000 |
||||
supports_encryption 0x20000 |
||||
named_streams 0x40000 |
||||
read_only_volume 0x80000 |
||||
sequential_write_once 0x00100000 |
||||
supports_transactions 0x00200000 |
||||
supports_hard_links 0x00400000 |
||||
supports_extended_attributes 0x00800000 |
||||
supports_open_by_file_id 0x01000000 |
||||
supports_usn_journal 0x02000000 |
||||
} { |
||||
if {$attr & $val} { |
||||
lappend attrs $sym |
||||
} |
||||
} |
||||
lappend result -attr $attrs |
||||
} |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
interp alias {} twapi::get_drive_info {} twapi::get_volume_info |
||||
|
||||
|
||||
# Check if disk has at least n bytes available for the user (NOT total free) |
||||
proc twapi::user_drive_space_available {drv space} { |
||||
return [expr {$space <= [lindex [get_drive_info $drv -useravail] 1]}] |
||||
} |
||||
|
||||
# Get the drive type |
||||
proc twapi::get_drive_type {drive} { |
||||
# set type [GetDriveType "[string trimright $drive :/\\]:\\"] |
||||
set type [GetDriveType [_drive_rootpath $drive]] |
||||
switch -exact -- $type { |
||||
0 { return unknown} |
||||
1 { return invalid} |
||||
2 { return removable} |
||||
3 { return fixed} |
||||
4 { return remote} |
||||
5 { return cdrom} |
||||
6 { return ramdisk} |
||||
} |
||||
} |
||||
|
||||
# Get list of drives |
||||
proc twapi::find_logical_drives {args} { |
||||
array set opts [parseargs args {type.arg}] |
||||
|
||||
set drives [list ] |
||||
foreach drive [_drivemask_to_drivelist [GetLogicalDrives]] { |
||||
if {(![info exists opts(type)]) || |
||||
[lsearch -exact $opts(type) [get_drive_type $drive]] >= 0} { |
||||
lappend drives $drive |
||||
} |
||||
} |
||||
return $drives |
||||
} |
||||
|
||||
twapi::proc* twapi::drive_ready {drive} { |
||||
uplevel #0 package require twapi_device |
||||
} { |
||||
set drive [string trimright $drive "/\\"] |
||||
if {[string length $drive] != 2 || [string index $drive 1] ne ":"} { |
||||
error "Invalid drive specification" |
||||
} |
||||
set drive "\\\\.\\$drive" |
||||
|
||||
# Do our best to avoid the Windows "Drive not ready" dialog |
||||
# 1 -> SEM_FAILCRITICALERRORS |
||||
if {[min_os_version 6]} { |
||||
set old_mode [SetErrorMode 1] |
||||
} |
||||
trap { |
||||
|
||||
# We will first try using IOCTL_STORAGE_CHECK_VERIFY2 as that is |
||||
# much faster and only needs FILE_READ_ATTRIBUTES access. |
||||
set error [catch { |
||||
set h [create_file $drive -access file_read_attributes \ |
||||
-createdisposition open_existing -share {read write}] |
||||
device_ioctl $h 0x2d0800; # IOCTL_STORAGE_CHECK_VERIFY2 |
||||
}] |
||||
if {[info exists h]} { |
||||
close_handle $h |
||||
} |
||||
if {! $error} { |
||||
return 1; # Device is ready |
||||
} |
||||
|
||||
# On error, try the older slower method. Note we now need |
||||
# GENERIC_READ access. (NOTE: FILE_READ_DATA will not work with some |
||||
# volume types) |
||||
unset -nocomplain h |
||||
set error [catch { |
||||
set h [create_file $drive -access generic_read \ |
||||
-createdisposition open_existing -share {read write}] |
||||
device_ioctl $h 0x2d4800; # IOCTL_STORAGE_CHECK_VERIFY |
||||
}] |
||||
if {[info exists h]} { |
||||
close_handle $h |
||||
} |
||||
if {! $error} { |
||||
return 1; # Device is ready |
||||
} |
||||
|
||||
# Remote shares sometimes return access denied with the above |
||||
# even when actually available. Try with good old file exists |
||||
# on root directory |
||||
return [file exists "[string range $drive end-1 end]\\"] |
||||
} finally { |
||||
if {[min_os_version 6]} { |
||||
SetErrorMode $old_mode |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
# Set the drive label |
||||
proc twapi::set_drive_label {drive label} { |
||||
SetVolumeLabel [_drive_rootpath $drive] $label |
||||
} |
||||
|
||||
# Maps a drive letter to the given path |
||||
proc twapi::map_drive_local {drive path args} { |
||||
array set opts [parseargs args {raw}] |
||||
|
||||
set drive [string range [_drive_rootpath $drive] 0 1] |
||||
DefineDosDevice $opts(raw) $drive [file nativename $path] |
||||
} |
||||
|
||||
|
||||
# Unmaps a drive letter |
||||
proc twapi::unmap_drive_local {drive args} { |
||||
array set opts [parseargs args { |
||||
path.arg |
||||
raw |
||||
} -nulldefault] |
||||
|
||||
set drive [string range [_drive_rootpath $drive] 0 1] |
||||
|
||||
set flags $opts(raw) |
||||
setbits flags 0x2; # DDD_REMOVE_DEFINITION |
||||
if {$opts(path) ne ""} { |
||||
setbits flags 0x4; # DDD_EXACT_MATCH_ON_REMOVE |
||||
} |
||||
DefineDosDevice $flags $drive [file nativename $opts(path)] |
||||
} |
||||
|
||||
|
||||
# Callback from C code |
||||
proc twapi::_filesystem_monitor_handler {id changes} { |
||||
variable _filesystem_monitor_scripts |
||||
if {[info exists _filesystem_monitor_scripts($id)]} { |
||||
return [uplevel #0 [linsert $_filesystem_monitor_scripts($id) end $id $changes]] |
||||
} else { |
||||
# Callback queued after close. Ignore |
||||
} |
||||
} |
||||
|
||||
# Monitor file changes |
||||
proc twapi::begin_filesystem_monitor {path script args} { |
||||
variable _filesystem_monitor_scripts |
||||
|
||||
array set opts [parseargs args { |
||||
{subtree.bool 0} |
||||
{filename.bool 0 0x1} |
||||
{dirname.bool 0 0x2} |
||||
{attr.bool 0 0x4} |
||||
{size.bool 0 0x8} |
||||
{write.bool 0 0x10} |
||||
{access.bool 0 0x20} |
||||
{create.bool 0 0x40} |
||||
{secd.bool 0 0x100} |
||||
{pattern.arg ""} |
||||
{patterns.arg ""} |
||||
} -maxleftover 0] |
||||
|
||||
if {[string length $opts(pattern)] && |
||||
[llength $opts(patterns)]} { |
||||
error "Options -pattern and -patterns are mutually exclusive. Note option -pattern is deprecated." |
||||
} |
||||
|
||||
if {[string length $opts(pattern)]} { |
||||
# Old style single pattern. Convert to new -patterns |
||||
set opts(patterns) [list "+$opts(pattern)"] |
||||
} |
||||
|
||||
# Change to use \ style path separator as that is what the file monitoring functions return |
||||
if {[llength $opts(patterns)]} { |
||||
foreach pat $opts(patterns) { |
||||
# Note / is replaced by \\ within the pattern |
||||
# since \ needs to be escaped with another \ within |
||||
# string match patterns |
||||
lappend pats [string map [list / \\\\] $pat] |
||||
} |
||||
set opts(patterns) $pats |
||||
} |
||||
|
||||
set flags [expr { $opts(filename) | $opts(dirname) | $opts(attr) | |
||||
$opts(size) | $opts(write) | $opts(access) | |
||||
$opts(create) | $opts(secd)}] |
||||
|
||||
if {! $flags} { |
||||
# If no options specified, default to all |
||||
set flags 0x17f |
||||
} |
||||
|
||||
set id [Twapi_RegisterDirectoryMonitor $path $opts(subtree) $flags $opts(patterns)] |
||||
set _filesystem_monitor_scripts($id) $script |
||||
return $id |
||||
} |
||||
|
||||
# Stop monitoring of files |
||||
proc twapi::cancel_filesystem_monitor {id} { |
||||
variable _filesystem_monitor_scripts |
||||
if {[info exists _filesystem_monitor_scripts($id)]} { |
||||
Twapi_UnregisterDirectoryMonitor $id |
||||
unset _filesystem_monitor_scripts($id) |
||||
} |
||||
} |
||||
|
||||
|
||||
# Get list of volumes |
||||
proc twapi::find_volumes {} { |
||||
set vols [list ] |
||||
set found 1 |
||||
# Assumes there has to be at least one volume |
||||
lassign [FindFirstVolume] handle vol |
||||
while {$found} { |
||||
lappend vols $vol |
||||
lassign [FindNextVolume $handle] found vol |
||||
} |
||||
FindVolumeClose $handle |
||||
return $vols |
||||
} |
||||
|
||||
# Get list of volume mount points |
||||
proc twapi::find_volume_mount_points {vol} { |
||||
set mntpts [list ] |
||||
set found 1 |
||||
trap { |
||||
lassign [FindFirstVolumeMountPoint $vol] handle mntpt |
||||
} onerror {TWAPI_WIN32 18} { |
||||
# ERROR_NO_MORE_FILES |
||||
# No volume mount points |
||||
return [list ] |
||||
} onerror {TWAPI_WIN32 3} { |
||||
# Volume does not support them |
||||
return [list ] |
||||
} |
||||
|
||||
# At least one volume found |
||||
while {$found} { |
||||
lappend mntpts $mntpt |
||||
lassign [FindNextVolumeMountPoint $handle] found mntpt |
||||
} |
||||
FindVolumeMountPointClose $handle |
||||
return $mntpts |
||||
} |
||||
|
||||
# Set volume mount point |
||||
proc twapi::mount_volume {volpt volname} { |
||||
# Note we don't use _drive_rootpath for trimming since may not be root path |
||||
SetVolumeMountPoint "[string trimright $volpt /\\]\\" "[string trimright $volname /\\]\\" |
||||
} |
||||
|
||||
# Delete volume mount point |
||||
proc twapi::unmount_volume {volpt} { |
||||
# Note we don't use _drive_rootpath for trimming since may not be root path |
||||
DeleteVolumeMountPoint "[string trimright $volpt /\\]\\" |
||||
} |
||||
|
||||
# Get the volume mounted at a volume mount point |
||||
proc twapi::get_mounted_volume_name {volpt} { |
||||
# Note we don't use _drive_rootpath for trimming since may not be root path |
||||
return [GetVolumeNameForVolumeMountPoint "[string trimright $volpt /\\]\\"] |
||||
} |
||||
|
||||
# Get the mount point corresponding to a given path |
||||
proc twapi::get_volume_mount_point_for_path {path} { |
||||
return [GetVolumePathName [file nativename $path]] |
||||
} |
||||
|
||||
|
||||
# Return the times associated with a file |
||||
proc twapi::get_file_times {fd args} { |
||||
array set opts [parseargs args { |
||||
all |
||||
mtime |
||||
ctime |
||||
atime |
||||
} -maxleftover 0] |
||||
|
||||
# Figure out if fd is a file path, Tcl channel or a handle |
||||
set close_handle false |
||||
if {[file exists $fd]} { |
||||
# It's a file name |
||||
# 0x02000000 -> FILE_FLAG_BACKUP_SEMANTICS, always required in case |
||||
# opening a directory (even if SeBackupPrivilege is not held |
||||
set h [create_file $fd -createdisposition open_existing -flags 0x02000000] |
||||
set close_handle true |
||||
} elseif {[catch {fconfigure $fd}]} { |
||||
# Not a Tcl channel, See if handle |
||||
if {[pointer? $fd]} { |
||||
set h $fd |
||||
} else { |
||||
error "$fd is not an existing file, handle or Tcl channel." |
||||
} |
||||
} else { |
||||
# Tcl channel |
||||
set h [get_tcl_channel_handle $fd read] |
||||
} |
||||
|
||||
set result [list ] |
||||
|
||||
foreach opt {ctime atime mtime} time [GetFileTime $h] { |
||||
if {$opts(all) || $opts($opt)} { |
||||
lappend result -$opt $time |
||||
} |
||||
} |
||||
|
||||
if {$close_handle} { |
||||
CloseHandle $h |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
|
||||
# Set the times associated with a file |
||||
proc twapi::set_file_times {fd args} { |
||||
|
||||
array set opts [parseargs args { |
||||
mtime.arg |
||||
ctime.arg |
||||
atime.arg |
||||
preserveatime |
||||
} -maxleftover 0 -nulldefault] |
||||
|
||||
if {$opts(atime) ne "" && $opts(preserveatime)} { |
||||
win32_error 87 "Cannot specify -atime and -preserveatime at the same time." |
||||
} |
||||
if {$opts(preserveatime)} { |
||||
set opts(atime) -1; # Meaning preserve access to original |
||||
} |
||||
|
||||
# Figure out if fd is a file path, Tcl channel or a handle |
||||
set close_handle false |
||||
if {[file exists $fd]} { |
||||
if {$opts(preserveatime)} { |
||||
win32_error 87 "Cannot specify -preserveatime unless file is specified as a Tcl channel or a Win32 handle." |
||||
} |
||||
|
||||
# It's a file name |
||||
# 0x02000000 -> FILE_FLAG_BACKUP_SEMANTICS, always required in case |
||||
# opening a directory (even if SeBackupPrivilege is not held |
||||
set h [create_file $fd -access {generic_write} -createdisposition open_existing -flags 0x02000000] |
||||
set close_handle true |
||||
} elseif {[catch {fconfigure $fd}]} { |
||||
# Not a Tcl channel, assume a handle |
||||
set h $fd |
||||
} else { |
||||
# Tcl channel |
||||
set h [get_tcl_channel_handle $fd read] |
||||
} |
||||
|
||||
SetFileTime $h $opts(ctime) $opts(atime) $opts(mtime) |
||||
|
||||
if {$close_handle} { |
||||
CloseHandle $h |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
# Convert a device based path to a normalized Win32 path with drive letters |
||||
proc twapi::normalize_device_rooted_path {path args} { |
||||
# TBD - keep a cache ? |
||||
# For example, we need to map \Device\HarddiskVolume1 to C: |
||||
# Can only do that by enumerating logical drives |
||||
set npath [file nativename $path] |
||||
if {![string match -nocase {\\Device\\*} $npath]} { |
||||
error "$path is not a valid device based path." |
||||
} |
||||
array set device_map {} |
||||
foreach drive [find_logical_drives] { |
||||
set device_path [lindex [lindex [get_volume_info $drive -device] 1] 0] |
||||
if {$device_path ne ""} { |
||||
set len [string length $device_path] |
||||
if {[string equal -nocase -length $len $path $device_path]} { |
||||
# Prefix matches, must be terminated by end or path separator |
||||
set ch [string index $npath $len] |
||||
if {$ch eq "" || $ch eq "\\"} { |
||||
set path ${drive}[string range $npath $len end] |
||||
if {[llength $args]} { |
||||
upvar [lindex $args 0] retvar |
||||
set retvar $path |
||||
return 1 |
||||
} else { |
||||
return $path |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if {[llength $args]} { |
||||
return 0 |
||||
} else { |
||||
error "Could not map device based path '$path'" |
||||
} |
||||
} |
||||
|
||||
proc twapi::flush_channel {chan} { |
||||
flush $chan |
||||
FlushFileBuffers [get_tcl_channel_handle $chan write] |
||||
} |
||||
|
||||
proc twapi::find_file_open {path args} { |
||||
variable _find_tokens |
||||
variable _find_counter |
||||
parseargs args { |
||||
{detail.arg basic {basic full}} |
||||
} -setvars -maxleftover 0 |
||||
|
||||
set detail_level [expr {$detail eq "basic" ? 1 : 0}] |
||||
if {[min_os_version 6 1]} { |
||||
set flags 2; # FIND_FIRST_EX_LARGE_FETCH - Win 7 |
||||
} else { |
||||
set flags 0 |
||||
} |
||||
# 0 -> search op. Could be specified as 1 to limit search to |
||||
# directories but that is only advisory and does not seem to work |
||||
# in many cases. So don't bother making it an option. |
||||
lassign [FindFirstFileEx $path $detail_level 0 "" $flags] handle entry |
||||
set token ff#[incr _find_counter] |
||||
set _find_tokens($token) [list Handle $handle Entry $entry] |
||||
return $token |
||||
} |
||||
|
||||
proc twapi::find_file_close {token} { |
||||
variable _find_tokens |
||||
if {[info exists _find_tokens($token)]} { |
||||
FindClose [dict get $_find_tokens($token) Handle] |
||||
unset _find_tokens($token) |
||||
} |
||||
return |
||||
} |
||||
|
||||
proc twapi::decode_file_attributes {attrs} { |
||||
return [_make_symbolic_bitmask $attrs { |
||||
archive 0x20 |
||||
compressed 0x800 |
||||
device 0x40 |
||||
directory 0x10 |
||||
encrypted 0x4000 |
||||
hidden 0x2 |
||||
integrity_stream 0x8000 |
||||
normal 0x80 |
||||
not_content_indexed 0x2000 |
||||
no_scrub_data 0x20000 |
||||
offline 0x1000 |
||||
readonly 0x1 |
||||
recall_on_data_access 0x400000 |
||||
recall_on_open 0x40000 |
||||
reparse_point 0x400 |
||||
sparse_file 0x200 |
||||
system 0x4 |
||||
temporary 0x100 |
||||
virtual 0x10000 |
||||
}] |
||||
} |
||||
|
||||
proc twapi::find_file_next {token varname} { |
||||
variable _find_tokens |
||||
if {![info exists _find_tokens($token)]} { |
||||
return false |
||||
} |
||||
if {[dict exists $_find_tokens($token) Entry]} { |
||||
set entry [dict get $_find_tokens($token) Entry] |
||||
dict unset _find_tokens($token) Entry |
||||
} else { |
||||
set entry [FindNextFile [dict get $_find_tokens($token) Handle]] |
||||
} |
||||
if {[llength $entry]} { |
||||
upvar 1 $varname result |
||||
set result [twine {attrs ctime atime mtime size reserve0 reserve1 name altname} $entry] |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
# Utility functions |
||||
|
||||
proc twapi::_drive_rootpath {drive} { |
||||
if {[_is_unc $drive]} { |
||||
# UNC |
||||
return "[string trimright $drive ]\\" |
||||
} else { |
||||
return "[string trimright $drive :/\\]:\\" |
||||
} |
||||
} |
||||
|
||||
proc twapi::_is_unc {path} { |
||||
return [expr {[string match {\\\\*} $path] || [string match //* $path]}] |
||||
} |
||||
|
||||
|
@ -0,0 +1,94 @@
|
||||
# |
||||
# Copyright (c) 2004, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
# |
||||
# TBD - tcl wrappers for semaphores |
||||
|
||||
namespace eval twapi { |
||||
} |
||||
|
||||
# |
||||
# Create and return a handle to a mutex |
||||
proc twapi::create_mutex {args} { |
||||
array set opts [parseargs args { |
||||
name.arg |
||||
secd.arg |
||||
inherit.bool |
||||
lock.bool |
||||
} -nulldefault -maxleftover 0] |
||||
|
||||
if {$opts(name) ne "" && $opts(lock)} { |
||||
# TBD - remove this mutex limitation |
||||
# This is not a Win32 limitation but ours. Would need to change the C |
||||
# implementation and our return format |
||||
error "Option -lock must not be specified as true if mutex is named" |
||||
} |
||||
|
||||
return [CreateMutex [_make_secattr $opts(secd) $opts(inherit)] $opts(lock) $opts(name)] |
||||
} |
||||
|
||||
# Get handle to an existing mutex |
||||
proc twapi::open_mutex {name args} { |
||||
array set opts [parseargs args { |
||||
{inherit.bool 0} |
||||
{access.arg {mutex_all_access}} |
||||
} -maxleftover 0] |
||||
|
||||
return [OpenMutex [_access_rights_to_mask $opts(access)] $opts(inherit) $name] |
||||
} |
||||
|
||||
# Lock the mutex |
||||
proc twapi::lock_mutex {h args} { |
||||
array set opts [parseargs args { |
||||
{wait.int -1} |
||||
}] |
||||
|
||||
return [wait_on_handle $h -wait $opts(wait)] |
||||
} |
||||
|
||||
|
||||
# Unlock the mutex |
||||
proc twapi::unlock_mutex {h} { |
||||
ReleaseMutex $h |
||||
} |
||||
|
||||
# |
||||
# Create and return a handle to a event |
||||
proc twapi::create_event {args} { |
||||
array set opts [parseargs args { |
||||
name.arg |
||||
secd.arg |
||||
inherit.bool |
||||
signalled.bool |
||||
manualreset.bool |
||||
existvar.arg |
||||
} -nulldefault -maxleftover 0] |
||||
|
||||
if {$opts(name) ne "" && $opts(signalled)} { |
||||
# Not clear whether event will be signalled state if it already |
||||
# existed but was not signalled |
||||
error "Option -signalled must not be specified as true if event is named." |
||||
} |
||||
|
||||
lassign [CreateEvent [_make_secattr $opts(secd) $opts(inherit)] $opts(manualreset) $opts(signalled) $opts(name)] h preexisted |
||||
if {$opts(manualreset)} { |
||||
# We want to catch attempts to wait on manual reset handles |
||||
set h [cast_handle $h HANDLE_MANUALRESETEVENT] |
||||
} |
||||
if {$opts(existvar) ne ""} { |
||||
upvar 1 $opts(existvar) existvar |
||||
set existvar $preexisted |
||||
} |
||||
|
||||
return $h |
||||
} |
||||
|
||||
interp alias {} twapi::set_event {} twapi::SetEvent |
||||
interp alias {} twapi::reset_event {} twapi::ResetEvent |
||||
|
||||
# Hack to work with the various build configuration. |
||||
if {[info commands ::twapi::get_version] ne ""} { |
||||
package provide twapi_synch [::twapi::get_version -patchlevel] |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,858 @@
|
||||
# |
||||
# Copyright (c) 2003-2018, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# General definitions and procs used by all TWAPI modules |
||||
|
||||
package require Tcl 8.5 |
||||
package require registry |
||||
|
||||
namespace eval twapi { |
||||
# Get rid of this ugliness - TBD |
||||
# Note this is different from NULL or {0 VOID} etc. It is more like |
||||
# a null token passed to functions that expect ptr to strings and |
||||
# allow the ptr to be NULL. |
||||
variable nullptr "__null__" |
||||
|
||||
variable scriptdir [file dirname [info script]] |
||||
|
||||
# Name of the var holding log messages in reflected in the C |
||||
# code, don't change it! |
||||
variable log_messages {} |
||||
|
||||
################################################################ |
||||
# Following procs are used early in init process so defined here |
||||
|
||||
# Throws a bad argument error that appears to come from caller's invocation |
||||
# (if default level is 2) |
||||
proc badargs! {msg {level 2}} { |
||||
return -level $level -code error -errorcode [list TWAPI BADARGS $msg] $msg |
||||
} |
||||
|
||||
proc lambda {arglist body {ns {}}} { |
||||
return [list ::apply [list $arglist $body $ns]] |
||||
} |
||||
|
||||
# Similar to lambda but takes additional parameters to be passed |
||||
# to the anonymous functin |
||||
proc lambda* {arglist body {ns {}} args} { |
||||
return [list ::apply [list $arglist $body $ns] {*}$args] |
||||
} |
||||
|
||||
# Rethrow original exception from inside a trap |
||||
proc rethrow {} { |
||||
return -code error -level 0 -options [twapi::trapoptions] [twapi::trapresult] |
||||
} |
||||
|
||||
# Dict lookup, returns default (from args) if not in dict and |
||||
# key itself if no defaults specified |
||||
proc dict* {d key args} { |
||||
if {[dict exists $d $key]} { |
||||
return [dict get $d $key] |
||||
} elseif {[llength $args]} { |
||||
return [lindex $args 0] |
||||
} else { |
||||
return $key |
||||
} |
||||
} |
||||
|
||||
proc dict! {d key {frame 0}} { |
||||
if {[dict exists $d $key]} { |
||||
return [dict get $d $key] |
||||
} else { |
||||
# frame is how must above the caller errorInfo must appear |
||||
return [badargs! "Bad value \"$key\". Must be one of [join [dict keys $d] {, }]" [incr frame 2]] |
||||
} |
||||
} |
||||
|
||||
|
||||
# Defines a proc with some initialization code |
||||
proc proc* {procname arglist initcode body} { |
||||
if {![string match ::* $procname]} { |
||||
set ns [uplevel 1 {namespace current}] |
||||
set procname ${ns}::$procname |
||||
} |
||||
set proc_def [format {proc %s {%s} {%s ; proc %s {%s} {%s} ; uplevel 1 [list %s] [lrange [info level 0] 1 end]}} $procname $arglist $initcode $procname $arglist $body $procname] |
||||
uplevel 1 $proc_def |
||||
} |
||||
|
||||
# Swap keys and values |
||||
proc swapl {l} { |
||||
set swapped {} |
||||
foreach {a b} $l { |
||||
lappend swapped $b $a |
||||
} |
||||
return $swapped |
||||
} |
||||
|
||||
# TBD - see if C would make faster |
||||
# Returns a list consisting of n'th index within each sublist element |
||||
# Should we allow n to be a nested index ? C impl may be harder |
||||
proc lpick {l {n 0}} { |
||||
set result {} |
||||
foreach e $l { |
||||
lappend result [lindex $e $n] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# Simple helper to treat lists as a stack |
||||
proc lpop {vl} { |
||||
upvar 1 $vl l |
||||
set top [lindex $l end] |
||||
# K combinator trick to reset l to allow lreplace to work in place |
||||
set l [lreplace $l [set l end] end] |
||||
return $top |
||||
} |
||||
|
||||
# twine list of n items |
||||
proc ntwine {fields l} { |
||||
set ntwine {} |
||||
foreach e $l { |
||||
lappend ntwine [twine $fields $e] |
||||
} |
||||
return $ntwine |
||||
} |
||||
|
||||
# Qualifies a name in context of caller's caller |
||||
proc callerns {name} { |
||||
if {[string match "::*" $name]} { |
||||
return $name |
||||
} |
||||
if {[info level] > 2} { |
||||
return [uplevel 2 namespace current]::$name |
||||
} else { |
||||
return ::$name |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Make twapi versions the same as the base module versions |
||||
set twapi::version(twapi) $::twapi::version(twapi_base) |
||||
|
||||
# |
||||
# log for tracing / debug messages. |
||||
proc twapi::debuglog_clear {} { |
||||
variable log_messages |
||||
set log_messages {} |
||||
} |
||||
|
||||
proc twapi::debuglog_enable {} { |
||||
catch {rename [namespace current]::debuglog {}} |
||||
interp alias {} [namespace current]::debuglog {} [namespace current]::Twapi_AppendLog |
||||
} |
||||
|
||||
proc twapi::debuglog_disable {} { |
||||
proc [namespace current]::debuglog {args} {} |
||||
} |
||||
|
||||
proc twapi::debuglog_get {} { |
||||
variable log_messages |
||||
return $log_messages |
||||
} |
||||
|
||||
# Logging disabled by default |
||||
twapi::debuglog_disable |
||||
|
||||
proc twapi::get_build_config {{key ""}} { |
||||
variable build_ids |
||||
array set config [GetTwapiBuildInfo] |
||||
|
||||
# This is actually a runtime config and might not have been initialized |
||||
if {[info exists ::twapi::use_tcloo_for_com]} { |
||||
if {$::twapi::use_tcloo_for_com} { |
||||
set config(comobj_ootype) tcloo |
||||
} else { |
||||
set config(comobj_ootype) metoo |
||||
} |
||||
} else { |
||||
set config(comobj_ootype) uninitialized |
||||
} |
||||
|
||||
if {$key eq ""} { |
||||
return [array get config] |
||||
} else { |
||||
if {![info exists config($key)]} { |
||||
error "key not known"; # Matches tcl::pkgconfig error message |
||||
} |
||||
return $config($key) |
||||
} |
||||
} |
||||
|
||||
# This matches the pkgconfig command as defined by Tcl_RegisterConfig |
||||
# TBD - Doc and test |
||||
proc twapi::pkgconfig {subcommand {arg {}}} { |
||||
if {$subcommand eq "list"} { |
||||
if {$arg ne ""} { |
||||
error {wrong # args: should be "twapi::pkgconfig list"} |
||||
} |
||||
return [dict keys [get_build_config]] |
||||
} elseif {$subcommand eq "get"} { |
||||
if {$arg eq ""} { |
||||
error {wrong # args: should be "twapi::pkgconfig get key"} |
||||
} |
||||
return [get_build_config $arg] |
||||
} else { |
||||
error {wrong # args: should be "tcl::pkgconfig subcommand ?arg?"} |
||||
} |
||||
} |
||||
|
||||
# TBD - document |
||||
proc twapi::support_report {} { |
||||
set report "Operating system: [get_os_description]\n" |
||||
append report "Processors: [get_processor_count]\n" |
||||
append report "WOW64: [wow64_process]\n" |
||||
append report "Virtualized: [virtualized_process]\n" |
||||
append report "System locale: [get_system_default_lcid], [get_system_default_langid]\n" |
||||
append report "User locale: [get_user_default_lcid], [get_user_default_langid]\n" |
||||
append report "Tcl version: [info patchlevel]\n" |
||||
append report "tcl_platform:\n" |
||||
foreach k [lsort -dictionary [array names ::tcl_platform]] { |
||||
append report " $k = $::tcl_platform($k)\n" |
||||
} |
||||
append report "TWAPI version: [get_version -patchlevel]\n" |
||||
array set a [get_build_config] |
||||
append report "TWAPI config:\n" |
||||
foreach k [lsort -dictionary [array names a]] { |
||||
append report " $k = $a($k)\n" |
||||
} |
||||
append report "\nDebug log:\n[join [debuglog_get] \n]\n" |
||||
} |
||||
|
||||
|
||||
# Returns a list of raw Windows API functions supported |
||||
proc twapi::list_raw_api {} { |
||||
set rawapi [list ] |
||||
foreach fn [info commands ::twapi::*] { |
||||
if {[regexp {^::twapi::([A-Z][^_]*)$} $fn ignore fn]} { |
||||
lappend rawapi $fn |
||||
} |
||||
} |
||||
return $rawapi |
||||
} |
||||
|
||||
|
||||
# Wait for $wait_ms milliseconds or until $script returns $guard. $gap_ms is |
||||
# time between retries to call $script |
||||
# TBD - write a version that will allow other events to be processed |
||||
proc twapi::wait {script guard wait_ms {gap_ms 10}} { |
||||
if {$gap_ms == 0} { |
||||
set gap_ms 10 |
||||
} |
||||
set end_ms [expr {[clock clicks -milliseconds] + $wait_ms}] |
||||
while {[clock clicks -milliseconds] < $end_ms} { |
||||
set script_result [uplevel $script] |
||||
if {[string equal $script_result $guard]} { |
||||
return 1 |
||||
} |
||||
after $gap_ms |
||||
} |
||||
# Reached limit, one last try |
||||
return [string equal [uplevel $script] $guard] |
||||
} |
||||
|
||||
# Get twapi version |
||||
proc twapi::get_version {args} { |
||||
variable version |
||||
array set opts [parseargs args {patchlevel}] |
||||
if {$opts(patchlevel)} { |
||||
return $version(twapi) |
||||
} else { |
||||
# Only return major, minor |
||||
set ver $version(twapi) |
||||
regexp {^([[:digit:]]+\.[[:digit:]]+)[.ab]} $version(twapi) - ver |
||||
return $ver |
||||
} |
||||
} |
||||
|
||||
# Set all elements of the array to specified value |
||||
proc twapi::_array_set_all {v_arr val} { |
||||
upvar $v_arr arr |
||||
foreach e [array names arr] { |
||||
set arr($e) $val |
||||
} |
||||
} |
||||
|
||||
# Check if any of the specified array elements are non-0 |
||||
proc twapi::_array_non_zero_entry {v_arr indices} { |
||||
upvar $v_arr arr |
||||
foreach i $indices { |
||||
if {$arr($i)} { |
||||
return 1 |
||||
} |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
# Check if any of the specified array elements are non-0 |
||||
# and return them as a list of options (preceded with -) |
||||
proc twapi::_array_non_zero_switches {v_arr indices all} { |
||||
upvar $v_arr arr |
||||
set result [list ] |
||||
foreach i $indices { |
||||
if {$all || ([info exists arr($i)] && $arr($i))} { |
||||
lappend result -$i |
||||
} |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
|
||||
# Bitmask operations on 32bit values |
||||
# The int() casts are to deal with hex-decimal sign extension issues |
||||
proc twapi::setbits {v_bits mask} { |
||||
upvar $v_bits bits |
||||
set bits [expr {int($bits) | int($mask)}] |
||||
return $bits |
||||
} |
||||
proc twapi::resetbits {v_bits mask} { |
||||
upvar $v_bits bits |
||||
set bits [expr {int($bits) & int(~ $mask)}] |
||||
return $bits |
||||
} |
||||
|
||||
# Return a bitmask corresponding to a list of symbolic and integer values |
||||
# If symvals is a single item, it is an array else a list of sym bitmask pairs |
||||
proc twapi::_parse_symbolic_bitmask {syms symvals} { |
||||
if {[llength $symvals] == 1} { |
||||
upvar $symvals lookup |
||||
} else { |
||||
array set lookup $symvals |
||||
} |
||||
set bits 0 |
||||
foreach sym $syms { |
||||
if {[info exists lookup($sym)]} { |
||||
set bits [expr {$bits | $lookup($sym)}] |
||||
} else { |
||||
set bits [expr {$bits | $sym}] |
||||
} |
||||
} |
||||
return $bits |
||||
} |
||||
|
||||
# Return a list of symbols corresponding to a bitmask |
||||
proc twapi::_make_symbolic_bitmask {bits symvals {append_unknown 1}} { |
||||
if {[llength $symvals] == 1} { |
||||
upvar $symvals lookup |
||||
set map [array get lookup] |
||||
} else { |
||||
set map $symvals |
||||
} |
||||
set symbits 0 |
||||
set symmask [list ] |
||||
foreach {sym val} $map { |
||||
if {$bits & $val} { |
||||
set symbits [expr {$symbits | $val}] |
||||
lappend symmask $sym |
||||
} |
||||
} |
||||
|
||||
# Get rid of bits that mapped to symbols |
||||
set bits [expr {$bits & ~$symbits}] |
||||
# If any left over, add them |
||||
if {$bits && $append_unknown} { |
||||
lappend symmask $bits |
||||
} |
||||
return $symmask |
||||
} |
||||
|
||||
# Return a bitmask corresponding to a list of symbolic and integer values |
||||
# If symvals is a single item, it is an array else a list of sym bitmask pairs |
||||
# Ditto for switches - an array or flat list of switch boolean pairs |
||||
proc twapi::_switches_to_bitmask {switches symvals {bits 0}} { |
||||
if {[llength $symvals] == 1} { |
||||
upvar $symvals lookup |
||||
} else { |
||||
array set lookup $symvals |
||||
} |
||||
if {[llength $switches] == 1} { |
||||
upvar $switches swtable |
||||
} else { |
||||
array set swtable $switches |
||||
} |
||||
|
||||
foreach {switch bool} [array get swtable] { |
||||
if {$bool} { |
||||
set bits [expr {$bits | $lookup($switch)}] |
||||
} else { |
||||
set bits [expr {$bits & ~ $lookup($switch)}] |
||||
} |
||||
} |
||||
return $bits |
||||
} |
||||
|
||||
# Return a list of switche bool pairs corresponding to a bitmask |
||||
proc twapi::_bitmask_to_switches {bits symvals} { |
||||
if {[llength $symvals] == 1} { |
||||
upvar $symvals lookup |
||||
set map [array get lookup] |
||||
} else { |
||||
set map $symvals |
||||
} |
||||
set symbits 0 |
||||
set symmask [list ] |
||||
foreach {sym val} $map { |
||||
if {$bits & $val} { |
||||
set symbits [expr {$symbits | $val}] |
||||
lappend symmask $sym 1 |
||||
} else { |
||||
lappend symmask $sym 0 |
||||
} |
||||
} |
||||
|
||||
return $symmask |
||||
} |
||||
|
||||
# Make and return a keyed list |
||||
proc twapi::kl_create {args} { |
||||
if {[llength $args] & 1} { |
||||
error "No value specified for keyed list field [lindex $args end]. A keyed list must have an even number of elements." |
||||
} |
||||
return $args |
||||
} |
||||
|
||||
# Make a keyed list given fields and values |
||||
interp alias {} twapi::kl_create2 {} twapi::twine |
||||
|
||||
# Set a key value |
||||
proc twapi::kl_set {kl field newval} { |
||||
set i 0 |
||||
foreach {fld val} $kl { |
||||
if {[string equal $fld $field]} { |
||||
incr i |
||||
return [lreplace $kl $i $i $newval] |
||||
} |
||||
incr i 2 |
||||
} |
||||
lappend kl $field $newval |
||||
return $kl |
||||
} |
||||
|
||||
# Check if a field exists in the keyed list |
||||
proc twapi::kl_vget {kl field varname} { |
||||
upvar $varname var |
||||
return [expr {! [catch {set var [kl_get $kl $field]}]}] |
||||
} |
||||
|
||||
# Remote/unset a key value |
||||
proc twapi::kl_unset {kl field} { |
||||
array set arr $kl |
||||
unset -nocomplain arr($field) |
||||
return [array get arr] |
||||
} |
||||
|
||||
# Compare two keyed lists |
||||
proc twapi::kl_equal {kl_a kl_b} { |
||||
array set a $kl_a |
||||
foreach {kb valb} $kl_b { |
||||
if {[info exists a($kb)] && ($a($kb) == $valb)} { |
||||
unset a($kb) |
||||
} else { |
||||
return 0 |
||||
} |
||||
} |
||||
if {[array size a]} { |
||||
return 0 |
||||
} else { |
||||
return 1 |
||||
} |
||||
} |
||||
|
||||
# Return the field names in a keyed list in the same order that they |
||||
# occured |
||||
proc twapi::kl_fields {kl} { |
||||
set fields [list ] |
||||
foreach {fld val} $kl { |
||||
lappend fields $fld |
||||
} |
||||
return $fields |
||||
} |
||||
|
||||
# Returns a flat list of the $field fields from a list |
||||
# of keyed lists |
||||
proc twapi::kl_flatten {list_of_kl args} { |
||||
set result {} |
||||
foreach kl $list_of_kl { |
||||
foreach field $args { |
||||
lappend result [kl_get $kl $field] |
||||
} |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
|
||||
# Return an array as a list of -index value pairs |
||||
proc twapi::_get_array_as_options {v_arr} { |
||||
upvar $v_arr arr |
||||
set result [list ] |
||||
foreach {index value} [array get arr] { |
||||
lappend result -$index $value |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# Parse a list of two integers or a x,y pair and return a list of two integers |
||||
# Generate exception on format error using msg |
||||
proc twapi::_parse_integer_pair {pair {msg "Invalid integer pair"}} { |
||||
if {[llength $pair] == 2} { |
||||
lassign $pair first second |
||||
if {[string is integer -strict $first] && |
||||
[string is integer -strict $second]} { |
||||
return [list $first $second] |
||||
} |
||||
} elseif {[regexp {^([[:digit:]]+),([[:digit:]]+)$} $pair dummy first second]} { |
||||
return [list $first $second] |
||||
} |
||||
|
||||
error "$msg: '$pair'. Should be a list of two integers or in the form 'x,y'" |
||||
} |
||||
|
||||
|
||||
# Convert file names by substituting \SystemRoot and \??\ sequences |
||||
proc twapi::_normalize_path {path} { |
||||
# Get rid of \??\ prefixes |
||||
regsub {^[\\/]\?\?[\\/](.*)} $path {\1} path |
||||
|
||||
# Replace leading \SystemRoot with real system root |
||||
if {[string match -nocase {[\\/]Systemroot*} $path] && |
||||
([string index $path 11] in [list "" / \\])} { |
||||
return [file join [twapi::GetSystemWindowsDirectory] [string range $path 12 end]] |
||||
} else { |
||||
return [file normalize $path] |
||||
} |
||||
} |
||||
|
||||
|
||||
# Convert seconds to a list {Year Month Day Hour Min Sec Ms} |
||||
# (Ms will always be zero). |
||||
proc twapi::_seconds_to_timelist {secs {gmt 0}} { |
||||
# For each field, we need to trim the leading zeroes |
||||
set result [list ] |
||||
foreach x [clock format $secs -format "%Y %m %e %k %M %S 0" -gmt $gmt] { |
||||
lappend result [scan $x %d] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# Convert local time list {Year Month Day Hour Min Sec Ms} to seconds |
||||
# (Ms field is ignored) |
||||
# TBD - fix this gmt issue - not clear whether caller expects gmt time |
||||
proc twapi::_timelist_to_seconds {timelist} { |
||||
return [clock scan [_timelist_to_timestring $timelist] -gmt false] |
||||
} |
||||
|
||||
# Convert local time list {Year Month Day Hour Min Sec Ms} to a time string |
||||
# (Ms field is ignored) |
||||
proc twapi::_timelist_to_timestring {timelist} { |
||||
if {[llength $timelist] < 6} { |
||||
error "Invalid time list format" |
||||
} |
||||
|
||||
return "[lindex $timelist 0]-[lindex $timelist 1]-[lindex $timelist 2] [lindex $timelist 3]:[lindex $timelist 4]:[lindex $timelist 5]" |
||||
} |
||||
|
||||
# Convert a time string to a time list |
||||
proc twapi::_timestring_to_timelist {timestring} { |
||||
return [_seconds_to_timelist [clock scan $timestring -gmt false]] |
||||
} |
||||
|
||||
# Parse raw memory like binary scan command |
||||
proc twapi::mem_binary_scan {mem off mem_sz args} { |
||||
uplevel [list binary scan [Twapi_ReadMemory 1 $mem $off $mem_sz]] $args |
||||
} |
||||
|
||||
|
||||
# Validate guid syntax |
||||
proc twapi::_validate_guid {guid} { |
||||
if {![Twapi_IsValidGUID $guid]} { |
||||
error "Invalid GUID syntax: '$guid'" |
||||
} |
||||
} |
||||
|
||||
# Validate uuid syntax |
||||
proc twapi::_validate_uuid {uuid} { |
||||
if {![regexp {^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$} $uuid]} { |
||||
error "Invalid UUID syntax: '$uuid'" |
||||
} |
||||
} |
||||
|
||||
# Extract a UCS-16 string from a binary. Cannot directly use |
||||
# encoding convertfrom because that will not stop at the terminating |
||||
# null. The UCS-16 assumed to be little endian. |
||||
proc twapi::_ucs16_binary_to_string {bin {off 0}} { |
||||
set bin [string range $bin $off end] |
||||
|
||||
# Find the terminating null. |
||||
set off [string first \0\0 $bin] |
||||
while {$off > 0 && ($off & 1)} { |
||||
# Offset off is odd and so crosses a char boundary, so not the |
||||
# terminating null. Step to the char boundary and start search again |
||||
incr off |
||||
set off [string first \0\0 $bin $off] |
||||
} |
||||
# off is offset of terminating UCS-16 null, or -1 if not found |
||||
if {$off < 0} { |
||||
# No terminator |
||||
return [encoding convertfrom unicode $bin] |
||||
} else { |
||||
return [encoding convertfrom unicode [string range $bin 0 $off-1]] |
||||
} |
||||
} |
||||
|
||||
# Extract a string from a binary. Cannot directly use |
||||
# encoding convertfrom because that will not stop at the terminating |
||||
# null. |
||||
proc twapi::_ascii_binary_to_string {bin {off 0}} { |
||||
set bin [string range $bin $off end] |
||||
|
||||
# Find the terminating null. |
||||
set off [string first \0 $bin] |
||||
|
||||
# off is offset of terminating null, or -1 if not found |
||||
if {$off < 0} { |
||||
# No terminator |
||||
return [encoding convertfrom ascii $bin] |
||||
} else { |
||||
return [encoding convertfrom ascii [string range $bin 0 $off-1]] |
||||
} |
||||
} |
||||
|
||||
|
||||
# Given a binary, return a GUID. The formatting is done as per the |
||||
# Windows StringFromGUID2 convention used by COM |
||||
proc twapi::_binary_to_guid {bin {off 0}} { |
||||
if {[binary scan $bin "@$off i s s H4 H12" g1 g2 g3 g4 g5] != 5} { |
||||
error "Invalid GUID binary" |
||||
} |
||||
|
||||
return [format "{%8.8X-%2.2hX-%2.2hX-%s}" $g1 $g2 $g3 [string toupper "$g4-$g5"]] |
||||
} |
||||
|
||||
# Given a guid string, return a GUID in binary form |
||||
proc twapi::_guid_to_binary {guid} { |
||||
_validate_guid $guid |
||||
lassign [split [string range $guid 1 end-1] -] g1 g2 g3 g4 g5 |
||||
return [binary format "i s s H4 H12" 0x$g1 0x$g2 0x$g3 $g4 $g5] |
||||
} |
||||
|
||||
# Return a guid from raw memory |
||||
proc twapi::_decode_mem_guid {mem {off 0}} { |
||||
return [_binary_to_guid [Twapi_ReadMemory 1 $mem $off 16]] |
||||
} |
||||
|
||||
# Convert a Windows registry value to Tcl form. mem is a raw |
||||
# memory object. off is the offset into the memory object to read. |
||||
# $type is a integer corresponding |
||||
# to the registry types |
||||
proc twapi::_decode_mem_registry_value {type mem len {off 0}} { |
||||
set type [expr {$type}]; # Convert hex etc. to decimal form |
||||
switch -exact -- $type { |
||||
1 - |
||||
2 { |
||||
return [list [expr {$type == 2 ? "expand_sz" : "sz"}] \ |
||||
[Twapi_ReadMemory 3 $mem $off $len 1]] |
||||
} |
||||
7 { |
||||
# Collect strings until we come across an empty string |
||||
# Note two nulls right at the start will result in |
||||
# an empty list. Should it result in a list with |
||||
# one empty string element? Most code on the web treats |
||||
# it as the former so we do too. |
||||
set multi [list ] |
||||
while {1} { |
||||
set str [Twapi_ReadMemory 3 $mem $off -1] |
||||
set n [string length $str] |
||||
# Check for out of bounds. Cannot check for this before |
||||
# actually reading the string since we do not know size |
||||
# of the string. |
||||
if {($len != -1) && ($off+$n+1) > $len} { |
||||
error "Possible memory corruption: read memory beyond specified memory size." |
||||
} |
||||
if {$n == 0} { |
||||
return [list multi_sz $multi] |
||||
} |
||||
lappend multi $str |
||||
# Move offset by length of the string and terminating null |
||||
# (times 2 since unicode and we want byte offset) |
||||
incr off [expr {2*($n+1)}] |
||||
} |
||||
} |
||||
4 { |
||||
if {$len < 4} { |
||||
error "Insufficient number of bytes to convert to integer." |
||||
} |
||||
return [list dword [Twapi_ReadMemory 0 $mem $off]] |
||||
} |
||||
5 { |
||||
if {$len < 4} { |
||||
error "Insufficient number of bytes to convert to big-endian integer." |
||||
} |
||||
set type "dword_big_endian" |
||||
set scanfmt "I" |
||||
set len 4 |
||||
} |
||||
11 { |
||||
if {$len < 8} { |
||||
error "Insufficient number of bytes to convert to wide integer." |
||||
} |
||||
set type "qword" |
||||
set scanfmt "w" |
||||
set len 8 |
||||
} |
||||
0 { set type "none" } |
||||
6 { set type "link" } |
||||
8 { set type "resource_list" } |
||||
3 { set type "binary" } |
||||
default { |
||||
error "Unsupported registry value type '$type'" |
||||
} |
||||
} |
||||
|
||||
set val [Twapi_ReadMemory 1 $mem $off $len] |
||||
if {[info exists scanfmt]} { |
||||
if {[binary scan $val $scanfmt val] != 1} { |
||||
error "Could not convert from binary value using scan format $scanfmt" |
||||
} |
||||
} |
||||
|
||||
return [list $type $val] |
||||
} |
||||
|
||||
|
||||
proc twapi::_log_timestamp {} { |
||||
return [clock format [clock seconds] -format "%a %T"] |
||||
} |
||||
|
||||
|
||||
# Helper for Net*Enum type functions taking a common set of arguments |
||||
proc twapi::_net_enum_helper {function args} { |
||||
if {[llength $args] == 1} { |
||||
set args [lindex $args 0] |
||||
} |
||||
|
||||
# -namelevel is used internally to indicate what level is to be used |
||||
# to retrieve names. -preargs and -postargs are used internally to |
||||
# add additional arguments at specific positions in the generic call. |
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
level.int |
||||
resume.int |
||||
filter.int |
||||
{namelevel.int 0} |
||||
{preargs.arg {}} |
||||
{postargs.arg {}} |
||||
{namefield.int 0} |
||||
fields.arg |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(level)]} { |
||||
set level $opts(level) |
||||
if {! [info exists opts(fields)]} { |
||||
badargs! "Option -fields must be specified if -level is specified" |
||||
} |
||||
} else { |
||||
set level $opts(namelevel) |
||||
} |
||||
|
||||
# Note later we need to know if opts(resume) was specified so |
||||
# don't change this to just default -resume to 0 above |
||||
if {[info exists opts(resume)]} { |
||||
set resumehandle $opts(resume) |
||||
} else { |
||||
set resumehandle 0 |
||||
} |
||||
|
||||
set moredata 1 |
||||
set result {} |
||||
while {$moredata} { |
||||
if {[info exists opts(filter)]} { |
||||
lassign [$function $opts(system) {*}$opts(preargs) $level $opts(filter) {*}$opts(postargs) $resumehandle] moredata resumehandle totalentries entries |
||||
} else { |
||||
lassign [$function $opts(system) {*}$opts(preargs) $level {*}$opts(postargs) $resumehandle] moredata resumehandle totalentries entries |
||||
} |
||||
# If caller does not want all data in one lump stop here |
||||
if {[info exists opts(resume)]} { |
||||
if {[info exists opts(level)]} { |
||||
return [list $moredata $resumehandle $totalentries [list $opts(fields) $entries]] |
||||
} else { |
||||
# Return flat list of names |
||||
return [list $moredata $resumehandle $totalentries [lpick $entries $opts(namefield)]] |
||||
} |
||||
} |
||||
|
||||
lappend result {*}$entries |
||||
} |
||||
|
||||
# Return what we have. Format depend on caller options. |
||||
if {[info exists opts(level)]} { |
||||
return [list $opts(fields) $result] |
||||
} else { |
||||
return [lpick $result $opts(namefield)] |
||||
} |
||||
} |
||||
|
||||
# If we are not being sourced from a executable resource, need to |
||||
# source the remaining support files. In the former case, they are |
||||
# automatically combined into one so the sourcing is not needed. |
||||
if {![info exists twapi::twapi_base_rc_sourced]} { |
||||
apply {{filelist} { |
||||
set dir [file dirname [info script]] |
||||
foreach f $filelist { |
||||
uplevel #0 [list source [file join $dir $f]] |
||||
} |
||||
}} {base.tcl handle.tcl win.tcl adsi.tcl} |
||||
} |
||||
|
||||
# Used in various matcher callbacks to signify always include etc. |
||||
# TBD - document |
||||
proc twapi::true {args} { |
||||
return true |
||||
} |
||||
|
||||
|
||||
namespace eval twapi { |
||||
# Get a handle to ourselves. This handle never need be closed |
||||
variable my_process_handle [GetCurrentProcess] |
||||
} |
||||
|
||||
# Only used internally for test validation. |
||||
# NOT the same as export_public_commands |
||||
proc twapi::_get_public_commands {} { |
||||
variable exports; # Populated via pkgIndex.tcl |
||||
if {[info exists exports]} { |
||||
return [concat {*}[dict values $exports]] |
||||
} else { |
||||
set cmds {} |
||||
foreach cmd [lsearch -regexp -inline -all [info commands [namespace current]::*] {::twapi::[a-z].*}] { |
||||
lappend cmds [namespace tail $cmd] |
||||
} |
||||
return $cmds |
||||
} |
||||
} |
||||
|
||||
proc twapi::export_public_commands {} { |
||||
variable exports; # Populated via pkgIndex.tcl |
||||
if {[info exists exports]} { |
||||
# Only export commands under twapi (e.g. not metoo) |
||||
dict for {ns cmds} $exports { |
||||
if {[regexp {^::twapi($|::)} $ns]} { |
||||
uplevel #0 [list namespace eval $ns [list namespace export {*}$cmds] |
||||
] |
||||
} |
||||
} |
||||
} else { |
||||
set cmds {} |
||||
foreach cmd [lsearch -regexp -inline -all [info commands [namespace current]::*] {::twapi::[a-z].*}] { |
||||
lappend cmds [namespace tail $cmd] |
||||
} |
||||
namespace eval [namespace current] "namespace export {*}$cmds" |
||||
} |
||||
} |
||||
|
||||
proc twapi::import_commands {} { |
||||
export_public_commands |
||||
uplevel namespace import twapi::* |
||||
} |
||||
|
Binary file not shown.
@ -0,0 +1,11 @@
|
||||
# -*- tcl -*- |
||||
namespace eval twapi { |
||||
variable version |
||||
set version(twapi) 4.7.2 |
||||
variable patchlevel 4.7.2 |
||||
variable package_name twapi |
||||
variable dll_base_name twapi[string map {. {}} 4.7.2] |
||||
variable scriptdir [file dirname [info script]] |
||||
} |
||||
|
||||
source [file join $twapi::scriptdir twapi.tcl] |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,131 @@
|
||||
# |
||||
# Copyright (c) 2012 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Contains common windowing and notification infrastructure |
||||
|
||||
namespace eval twapi { |
||||
variable null_hwin "" |
||||
|
||||
# Windows messages that are directly accessible from script. These |
||||
# are handled by the default notifications window and passed to |
||||
# the twapi::_script_wm_handler. These messages must be in the |
||||
# range (1056 = 1024+32) - (1024+32+31) (see twapi_wm.h) |
||||
variable _wm_script_msgs |
||||
array set _wm_script_msgs { |
||||
TASKBAR_RESTART 1031 |
||||
NOTIFY_ICON_CALLBACK 1056 |
||||
} |
||||
proc _get_script_wm {tok} { |
||||
variable _wm_script_msgs |
||||
return $_wm_script_msgs($tok) |
||||
} |
||||
} |
||||
|
||||
# Backward compatibility aliases |
||||
interp alias {} twapi::GetWindowLong {} twapi::GetWindowLongPtr |
||||
interp alias {} twapi::SetWindowLong {} twapi::SetWindowLongPtr |
||||
|
||||
# Return the long value at the given index |
||||
# This is a raw function, and should generally be used only to get |
||||
# non-system defined indices |
||||
proc twapi::get_window_long {hwin index} { |
||||
return [GetWindowLongPtr $hwin $index] |
||||
} |
||||
|
||||
# Set the long value at the given index and return the previous value |
||||
# This is a raw function, and should generally be used only to get |
||||
# non-system defined indices |
||||
proc twapi::set_window_long {hwin index val} { |
||||
set oldval [SetWindowLongPtr $hwin $index $val] |
||||
} |
||||
|
||||
# Set the user data associated with a window. Returns the previous value |
||||
proc twapi::set_window_userdata {hwin val} { |
||||
# GWL_USERDATA -> -21 |
||||
return [SetWindowLongPtr $hwin -21 $val] |
||||
} |
||||
|
||||
# Attaches to the thread queue of the thread owning $hwin and executes |
||||
# script in the caller's scope |
||||
proc twapi::_attach_hwin_and_eval {hwin script} { |
||||
set me [GetCurrentThreadId] |
||||
set hwin_tid [lindex [GetWindowThreadProcessId $hwin] 0] |
||||
if {$hwin_tid == 0} { |
||||
error "Window $hwin does not exist or could not get its thread owner" |
||||
} |
||||
|
||||
# Cannot (and no need to) attach to oneself so just exec script directly |
||||
if {$me == $hwin_tid} { |
||||
return [uplevel 1 $script] |
||||
} |
||||
|
||||
trap { |
||||
if {![AttachThreadInput $me $hwin_tid 1]} { |
||||
error "Could not attach to thread input for window $hwin" |
||||
} |
||||
set result [uplevel 1 $script] |
||||
} finally { |
||||
AttachThreadInput $me $hwin_tid 0 |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
proc twapi::_register_script_wm_handler {msg cmdprefix {overwrite 0}} { |
||||
variable _wm_registrations |
||||
|
||||
# Ensure notification window exists |
||||
twapi::Twapi_GetNotificationWindow |
||||
|
||||
# The incr ensures decimal format |
||||
# The lrange ensure proper list format |
||||
if {$overwrite} { |
||||
set _wm_registrations([incr msg 0]) [list [lrange $cmdprefix 0 end]] |
||||
} else { |
||||
lappend _wm_registrations([incr msg 0]) [lrange $cmdprefix 0 end] |
||||
} |
||||
} |
||||
|
||||
proc twapi::_unregister_script_wm_handler {msg cmdprefix} { |
||||
variable _wm_registrations |
||||
|
||||
# The incr ensures decimal format |
||||
incr msg 0 |
||||
# The lrange ensure proper list format |
||||
if {[info exists _wm_registrations($msg)]} { |
||||
set _wm_registrations($msg) [lsearch -exact -inline -not -all $_wm_registrations($msg) [lrange $cmdprefix 0 end]] |
||||
} |
||||
} |
||||
|
||||
# Handles notifications from the common window for script level windows |
||||
# messages (see win.c) |
||||
proc twapi::_script_wm_handler {msg wparam lparam msgpos ticks} { |
||||
variable _wm_registrations |
||||
|
||||
set code 0 |
||||
if {[info exists _wm_registrations($msg)]} { |
||||
foreach handler $_wm_registrations($msg) { |
||||
set code [catch {uplevel #0 [linsert $handler end $msg $wparam $lparam $msgpos $ticks]} msg] |
||||
switch -exact -- $code { |
||||
1 { |
||||
# TBD - should remaining handlers be called even on error ? |
||||
after 0 [list error $msg $::errorInfo $::errorCode] |
||||
break |
||||
} |
||||
3 { |
||||
break; # Ignore remaining handlers |
||||
} |
||||
default { |
||||
# Keep going |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
# TBD - debuglog - no handler for $msg |
||||
} |
||||
|
||||
return |
||||
} |
@ -0,0 +1,304 @@
|
||||
# |
||||
# Copyright (c) 2012, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
# Routines to unify old and new Windows event log APIs |
||||
|
||||
namespace eval twapi { |
||||
# Dictionary to map eventlog consumer handles to various related info |
||||
# The primary key is the read handle to the event channel/source. |
||||
# Nested keys depend on OS version |
||||
variable _winlog_handles |
||||
} |
||||
|
||||
proc twapi::winlog_open {args} { |
||||
variable _winlog_handles |
||||
|
||||
# TBD - document -authtype |
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
channel.arg |
||||
file.arg |
||||
{authtype.arg 0} |
||||
{direction.arg forward {forward backward}} |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(file)] && |
||||
($opts(system) ne "" || [info exists opts(channel)])} { |
||||
error "Option '-file' cannot be used with '-channel' or '-system'" |
||||
} else { |
||||
if {![info exists opts(channel)]} { |
||||
set opts(channel) "Application" |
||||
} |
||||
} |
||||
|
||||
if {[min_os_version 6]} { |
||||
# Use new Vista APIs |
||||
if {[info exists opts(file)]} { |
||||
set hsess NULL |
||||
set hq [evt_query -file $opts(file) -ignorequeryerrors] |
||||
} else { |
||||
if {$opts(system) eq ""} { |
||||
set hsess [twapi::evt_local_session] |
||||
} else { |
||||
set hsess [evt_open_session $opts(system) -authtype $opts(authtype)] |
||||
} |
||||
# evt_query will not read new events from a channel once |
||||
# eof is reached. So if reading in forward direction, we use |
||||
# evt_subscribe. Backward it does not matter. |
||||
if {$opts(direction) eq "forward"} { |
||||
lassign [evt_subscribe $opts(channel) -session $hsess -ignorequeryerrors -includeexisting] hq signal |
||||
dict set _winlog_handles $hq signal $signal |
||||
} else { |
||||
set hq [evt_query -session $hsess -channel $opts(channel) -ignorequeryerrors -direction $opts(direction)] |
||||
} |
||||
} |
||||
|
||||
dict set _winlog_handles $hq session $hsess |
||||
} else { |
||||
if {[info exists opts(file)]} { |
||||
set hq [eventlog_open -file $opts(file)] |
||||
dict set _winlog_handles $hq channel $opts(file) |
||||
} else { |
||||
set hq [eventlog_open -system $opts(system) -source $opts(channel)] |
||||
dict set _winlog_handles $hq channel $opts(channel) |
||||
} |
||||
dict set _winlog_handles $hq direction $opts(direction) |
||||
} |
||||
return $hq |
||||
} |
||||
|
||||
proc twapi::winlog_close {hq} { |
||||
variable _winlog_handles |
||||
|
||||
if {! [dict exists $_winlog_handles $hq]} { |
||||
error "Invalid event consumer handler '$hq'" |
||||
} |
||||
|
||||
if {[dict exists $_winlog_handles $hq signal]} { |
||||
# Catch in case app has closed event directly, for |
||||
# example when returned through winlog_subscribe |
||||
catch {close_handle [dict get $_winlog_handles $hq signal]} |
||||
} |
||||
if {[min_os_version 6]} { |
||||
set hsess [dict get $_winlog_handles $hq session] |
||||
evt_close $hq |
||||
evt_close_session $hsess |
||||
} else { |
||||
eventlog_close $hq |
||||
} |
||||
|
||||
dict unset _winlog_handles $hq |
||||
return |
||||
} |
||||
|
||||
proc twapi::winlog_event_count {args} { |
||||
# TBD - document and -authtype |
||||
array set opts [parseargs args { |
||||
{system.arg ""} |
||||
channel.arg |
||||
file.arg |
||||
{authtype.arg 0} |
||||
} -maxleftover 0] |
||||
|
||||
if {[info exists opts(file)] && |
||||
($opts(system) ne "" || [info exists opts(channel)])} { |
||||
error "Option '-file' cannot be used with '-channel' or '-system'" |
||||
} else { |
||||
if {![info exists opts(channel)]} { |
||||
set opts(channel) "Application" |
||||
} |
||||
} |
||||
|
||||
if {[min_os_version 6]} { |
||||
# Use new Vista APIs |
||||
trap { |
||||
if {[info exists opts(file)]} { |
||||
set hsess NULL |
||||
set hevl [evt_open_log_info -file $opts(file)] |
||||
} else { |
||||
if {$opts(system) eq ""} { |
||||
set hsess [twapi::evt_local_session] |
||||
} else { |
||||
set hsess [evt_open_session $opts(system) -authtype $opts(authtype)] |
||||
} |
||||
set hevl [evt_open_log_info -session $hsess -channel $opts(channel)] |
||||
} |
||||
return [lindex [evt_log_info $hevl -numberoflogrecords] 1] |
||||
} finally { |
||||
if {[info exists hsess]} { |
||||
evt_close_session $hsess |
||||
} |
||||
if {[info exists hevl]} { |
||||
evt_close $hevl |
||||
} |
||||
} |
||||
} else { |
||||
if {[info exists opts(file)]} { |
||||
set hevl [eventlog_open -file $opts(file)] |
||||
} else { |
||||
set hevl [eventlog_open -system $opts(system) -source $opts(channel)] |
||||
} |
||||
|
||||
trap { |
||||
return [eventlog_count $hevl] |
||||
} finally { |
||||
eventlog_close $hevl |
||||
} |
||||
} |
||||
} |
||||
|
||||
if {[twapi::min_os_version 6]} { |
||||
|
||||
proc twapi::winlog_read {hq args} { |
||||
parseargs args { |
||||
{lcid.int 0} |
||||
} -setvars -maxleftover 0 |
||||
|
||||
# TBD - is 10 an appropriate number of events to read? |
||||
set events [evt_next $hq -timeout 0 -count 10 -status status] |
||||
if {[llength $events]} { |
||||
trap { |
||||
set result [evt_decode_events $events -lcid $lcid -ignorestring "" -message -levelname -taskname] |
||||
} finally { |
||||
evt_close {*}$events |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# No events were returned. Check status whether it is fatal error |
||||
# or not. SUCCESS, NO_MORE_ITEMS, TIMEOUT, INVALID_OPERATION |
||||
# are acceptable. This last happens when another EvtNext is done |
||||
# after an NO_MORE_ITEMS is already returned. |
||||
if {$status == 0 || $status == 259 || $status == 1460 || $status == 4317} { |
||||
# Even though $events is empty, still pass it in so it returns |
||||
# an empty record array in the correct format. |
||||
return [evt_decode_events $events -lcid $lcid -ignorestring "" -message -levelname -taskname] |
||||
} else { |
||||
win32_error $status |
||||
} |
||||
} |
||||
|
||||
proc twapi::winlog_subscribe {channelpath} { |
||||
variable _winlog_handles |
||||
lassign [evt_subscribe $channelpath -ignorequeryerrors] hq signal |
||||
dict set _winlog_handles $hq signal $signal |
||||
dict set _winlog_handles $hq session NULL; # local session |
||||
return [list $hq $signal] |
||||
} |
||||
|
||||
interp alias {} twapi::winlog_clear {} twapi::evt_clear_log |
||||
|
||||
proc twapi::winlog_backup {channel outpath} { |
||||
evt_export_log $outpath -channel $channel |
||||
return |
||||
} |
||||
|
||||
} else { |
||||
|
||||
proc twapi::winlog_read {hq args} { |
||||
parseargs args { |
||||
{lcid.int 0} |
||||
} -setvars -maxleftover 0 |
||||
|
||||
variable _winlog_handles |
||||
set fields {-channel -taskname -message -providername -eventid -level -levelname -eventrecordid -computer -sid -timecreated} |
||||
set values {} |
||||
set channel [dict get $_winlog_handles $hq channel] |
||||
foreach evl [eventlog_read $hq -direction [dict get $_winlog_handles $hq direction]] { |
||||
# Note order must be same as fields above |
||||
lappend values \ |
||||
[list \ |
||||
$channel \ |
||||
[eventlog_format_category $evl -langid $lcid] \ |
||||
[eventlog_format_message $evl -langid $lcid -width -1] \ |
||||
[dict get $evl -source] \ |
||||
[dict get $evl -eventid] \ |
||||
[dict get $evl -level] \ |
||||
[dict get $evl -type] \ |
||||
[dict get $evl -recordnum] \ |
||||
[dict get $evl -system] \ |
||||
[dict get $evl -sid] \ |
||||
[secs_since_1970_to_large_system_time [dict get $evl -timewritten]]] |
||||
} |
||||
return [list $fields $values] |
||||
} |
||||
|
||||
proc twapi::winlog_subscribe {source} { |
||||
variable _winlog_handles |
||||
lassign [eventlog_subscribe $source] hq hevent |
||||
dict set _winlog_handles $hq channel $source |
||||
dict set _winlog_handles $hq direction forward |
||||
dict set _winlog_handles $hq signal $hevent |
||||
return [list $hq $hevent] |
||||
} |
||||
|
||||
proc twapi::winlog_clear {source args} { |
||||
set hevl [eventlog_open -source $source] |
||||
trap { |
||||
eventlog_clear $hevl {*}$args |
||||
} finally { |
||||
eventlog_close $hevl |
||||
} |
||||
return |
||||
} |
||||
|
||||
proc twapi::winlog_backup {source outpath} { |
||||
set hevl [eventlog_open -source $source] |
||||
trap { |
||||
eventlog_backup $hevl $outpath |
||||
} finally { |
||||
eventlog_close $hevl |
||||
} |
||||
return |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
proc twapi::_winlog_dump_list {{channels {Application System Security}} {atomize 0}} { |
||||
set evlist {} |
||||
foreach channel $channels { |
||||
set hevl [winlog_open -channel $channel] |
||||
trap { |
||||
while {[llength [set events [winlog_read $hevl]]]} { |
||||
foreach e [recordarray getlist $events -format dict] { |
||||
if {$atomize} { |
||||
dict set ev -message [atomize [dict get $e -message]] |
||||
dict set ev -levelname [atomize [dict get $e -levelname]] |
||||
dict set ev -channel [atomize [dict get $e -channel]] |
||||
dict set ev -providername [atomize [dict get $e -providername]] |
||||
dict set ev -taskname [atomize [dict get $e -taskname]] |
||||
dict set ev -eventid [atomize [dict get $e -eventid]] |
||||
dict set ev -account [atomize [dict get $e -userid]] |
||||
} else { |
||||
dict set ev -message [dict get $e -message] |
||||
dict set ev -levelname [dict get $e -levelname] |
||||
dict set ev -channel [dict get $e -channel] |
||||
dict set ev -providername [dict get $e -providername] |
||||
dict set ev -taskname [dict get $e -taskname] |
||||
dict set ev -eventid [dict get $e -eventid] |
||||
dict set ev -account [dict get $e -userid] |
||||
} |
||||
lappend evlist $ev |
||||
} |
||||
} |
||||
} finally { |
||||
winlog_close $hevl |
||||
} |
||||
} |
||||
return $evlist |
||||
} |
||||
|
||||
proc twapi::_winlog_dump {{channel Application} {fd stdout}} { |
||||
set hevl [winlog_open -channel $channel] |
||||
while {[llength [set events [winlog_read $hevl]]]} { |
||||
# print out each record |
||||
foreach ev [recordarray getlist $events -format dict] { |
||||
puts $fd "[dict get $ev -timecreated] [dict get $ev -providername]: [dict get $ev -message]" |
||||
} |
||||
} |
||||
winlog_close $hevl |
||||
} |
@ -0,0 +1,113 @@
|
||||
# |
||||
# Copyright (c) 2004-2012, Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
|
||||
# TBD - document and test |
||||
proc twapi::get_active_console_tssession {} { |
||||
return [WTSGetActiveConsoleSessionId] |
||||
} |
||||
|
||||
proc twapi::get_current_window_station_handle {} { |
||||
return [GetProcessWindowStation] |
||||
} |
||||
|
||||
# Get the handle to a window station |
||||
proc twapi::get_window_station_handle {winsta args} { |
||||
array set opts [parseargs args { |
||||
inherit.bool |
||||
{access.arg generic_read} |
||||
} -nulldefault] |
||||
|
||||
set access_rights [_access_rights_to_mask $opts(access)] |
||||
|
||||
return [OpenWindowStation $winsta $opts(inherit) $access_rights] |
||||
} |
||||
|
||||
|
||||
# Close a window station handle |
||||
proc twapi::close_window_station_handle {hwinsta} { |
||||
# Trying to close our window station handle will generate an error |
||||
if {$hwinsta != [get_current_window_station_handle]} { |
||||
CloseWindowStation $hwinsta |
||||
} |
||||
return |
||||
} |
||||
|
||||
# List all window stations |
||||
proc twapi::find_window_stations {} { |
||||
return [EnumWindowStations] |
||||
} |
||||
|
||||
|
||||
# Enumerate desktops in a window station |
||||
proc twapi::find_desktops {args} { |
||||
array set opts [parseargs args {winsta.arg}] |
||||
|
||||
if {[info exists opts(winsta)]} { |
||||
set hwinsta [get_window_station_handle $opts(winsta)] |
||||
} else { |
||||
set hwinsta [get_current_window_station_handle] |
||||
} |
||||
|
||||
trap { |
||||
return [EnumDesktops $hwinsta] |
||||
} finally { |
||||
# Note close_window_station_handle protects against |
||||
# hwinsta being the current window station handle so |
||||
# we do not need to do that check here |
||||
close_window_station_handle $hwinsta |
||||
} |
||||
} |
||||
|
||||
|
||||
# Get the handle to a desktop |
||||
proc twapi::get_desktop_handle {desk args} { |
||||
array set opts [parseargs args { |
||||
inherit.bool |
||||
allowhooks.bool |
||||
{access.arg generic_read} |
||||
} -nulldefault] |
||||
|
||||
set access_mask [_access_rights_to_mask $opts(access)] |
||||
|
||||
# If certain access rights are specified, we must add certain other |
||||
# access rights. See OpenDesktop SDK docs |
||||
set access_rights [_access_mask_to_rights $access_mask] |
||||
if {"read_control" in $access_rights || |
||||
"write_dacl" in $access_rights || |
||||
"write_owner" in $access_rights} { |
||||
lappend access_rights desktop_readobject desktop_writeobjects |
||||
set access_mask [_access_rights_to_mask $opts(access)] |
||||
} |
||||
|
||||
return [OpenDesktop $desk $opts(allowhooks) $opts(inherit) $access_mask] |
||||
} |
||||
|
||||
# Close the desktop handle |
||||
proc twapi::close_desktop_handle {hdesk} { |
||||
CloseDesktop $hdesk |
||||
} |
||||
|
||||
# Set the process window station |
||||
proc twapi::set_process_window_station {hwinsta} { |
||||
SetProcessWindowStation $hwinsta |
||||
} |
||||
|
||||
proc twapi::get_desktop_user_sid {hdesk} { |
||||
return [GetUserObjectInformation $hdesk 4] |
||||
} |
||||
|
||||
proc twapi::get_window_station_user_sid {hwinsta} { |
||||
return [GetUserObjectInformation $hwinsta 4] |
||||
} |
||||
|
||||
proc twapi::get_desktop_name {hdesk} { |
||||
return [GetUserObjectInformation $hdesk 2] |
||||
} |
||||
|
||||
proc twapi::get_window_station_name {hwinsta} { |
||||
return [GetUserObjectInformation $hwinsta 2] |
||||
} |
@ -0,0 +1,223 @@
|
||||
# |
||||
# Copyright (c) 2012 Ashok P. Nadkarni |
||||
# All rights reserved. |
||||
# |
||||
# See the file LICENSE for license |
||||
|
||||
package require twapi_com |
||||
|
||||
# TBD - document? |
||||
|
||||
twapi::class create ::twapi::IMofCompilerProxy { |
||||
superclass ::twapi::IUnknownProxy |
||||
|
||||
constructor {args} { |
||||
if {[llength $args] == 0} { |
||||
set args [list [::twapi::com_create_instance "{6daf9757-2e37-11d2-aec9-00c04fb68820}" -interface IMofCompiler -raw]] |
||||
} |
||||
next {*}$args |
||||
} |
||||
|
||||
method CompileBuffer args { |
||||
my variable _ifc |
||||
return [::twapi::IMofCompiler_CompileBuffer $_ifc {*}$args] |
||||
} |
||||
|
||||
method CompileFile args { |
||||
my variable _ifc |
||||
return [::twapi::IMofCompiler_CompileFile $_ifc {*}$args] |
||||
} |
||||
|
||||
method CreateBMOF args { |
||||
my variable _ifc |
||||
return [::twapi::IMofCompiler_CreateBMOF $_ifc {*}$args] |
||||
} |
||||
|
||||
twapi_exportall |
||||
} |
||||
|
||||
|
||||
# |
||||
# Get WMI service - TBD document |
||||
proc twapi::wmi_root {args} { |
||||
array set opts [parseargs args { |
||||
{root.arg cimv2} |
||||
{impersonationlevel.arg impersonate {default anonymous identify delegate impersonate} } |
||||
} -maxleftover 0] |
||||
|
||||
# TBD - any injection attacks possible ? Need to quote ? |
||||
return [comobj_object "winmgmts:{impersonationLevel=$opts(impersonationlevel)}!//./root/$opts(root)"] |
||||
} |
||||
# Backwards compat |
||||
proc twapi::_wmi {{top cimv2}} { |
||||
return [wmi_root -root $top] |
||||
} |
||||
|
||||
# TBD - see if using ExecQuery would be faster if it supports all the options |
||||
proc twapi::wmi_collect_classes {swbemservices args} { |
||||
array set opts [parseargs args { |
||||
{ancestor.arg {}} |
||||
shallow |
||||
first |
||||
matchproperties.arg |
||||
matchsystemproperties.arg |
||||
matchqualifiers.arg |
||||
{collector.arg {lindex}} |
||||
} -maxleftover 0] |
||||
|
||||
|
||||
# Create a forward only enumerator for efficiency |
||||
# wbemFlagUseAmendedQualifiers | wbemFlagReturnImmediately | wbemFlagForwardOnly |
||||
set flags 0x20030 |
||||
if {$opts(shallow)} { |
||||
incr flags 1; # 0x1 -> wbemQueryFlagShallow |
||||
} |
||||
|
||||
set classes [$swbemservices SubclassesOf $opts(ancestor) $flags] |
||||
set matches {} |
||||
set delete_on_error {} |
||||
twapi::trap { |
||||
$classes -iterate class { |
||||
set matched 1 |
||||
foreach {opt fn} { |
||||
matchproperties Properties_ |
||||
matchsystemproperties SystemProperties_ |
||||
matchqualifiers Qualifiers_ |
||||
} { |
||||
if {[info exists opts($opt)]} { |
||||
foreach {name matcher} $opts($opt) { |
||||
if {[catch { |
||||
if {! [{*}$matcher [$class -with [list [list -get $fn] [list Item $name]] Value]]} { |
||||
set matched 0 |
||||
break; # Value does not match |
||||
} |
||||
} msg ]} { |
||||
# TBD - log debug error if not property found |
||||
# No such property or no access |
||||
set matched 0 |
||||
break |
||||
} |
||||
} |
||||
} |
||||
if {! $matched} { |
||||
# Already failed to match, no point continuing looping |
||||
break |
||||
} |
||||
} |
||||
|
||||
if {$matched} { |
||||
# Note collector code is responsible for disposing |
||||
# of $class as appropriate. But we take care of deleting |
||||
# when an error occurs after some accumulation has |
||||
# already occurred. |
||||
lappend delete_on_error $class |
||||
if {$opts(first)} { |
||||
return [{*}$opts(collector) $class] |
||||
} else { |
||||
lappend matches [{*}$opts(collector) $class] |
||||
} |
||||
} else { |
||||
$class destroy |
||||
} |
||||
} |
||||
} onerror {} { |
||||
foreach class $delete_on_error { |
||||
if {[comobj? $class]} { |
||||
$class destroy |
||||
} |
||||
} |
||||
rethrow |
||||
} finally { |
||||
$classes destroy |
||||
} |
||||
|
||||
return $matches |
||||
} |
||||
|
||||
proc twapi::wmi_extract_qualifier {qual} { |
||||
foreach prop {name value isamended propagatestoinstance propagatestosubclass isoverridable} { |
||||
dict set result $prop [$qual -get $prop] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
proc twapi::wmi_extract_property {propobj} { |
||||
foreach prop {name value cimtype isarray islocal origin} { |
||||
dict set result $prop [$propobj -get $prop] |
||||
} |
||||
|
||||
$propobj -with Qualifiers_ -iterate -cleanup qual { |
||||
set rec [wmi_extract_qualifier $qual] |
||||
dict set result qualifiers [string tolower [dict get $rec name]] $rec |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
proc twapi::wmi_extract_systemproperty {propobj} { |
||||
# Separate from wmi_extract_property because system properties do not |
||||
# have Qualifiers_ |
||||
foreach prop {name value cimtype isarray islocal origin} { |
||||
dict set result $prop [$propobj -get $prop] |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
|
||||
proc twapi::wmi_extract_method {mobj} { |
||||
foreach prop {name origin} { |
||||
dict set result $prop [$mobj -get $prop] |
||||
} |
||||
|
||||
# The InParameters and OutParameters properties are SWBEMObjects |
||||
# the properties of which describe the parameters. |
||||
foreach inout {inparameters outparameters} { |
||||
set paramsobj [$mobj -get $inout] |
||||
if {[$paramsobj -isnull]} { |
||||
dict set result $inout {} |
||||
} else { |
||||
$paramsobj -with Properties_ -iterate -cleanup pobj { |
||||
set rec [wmi_extract_property $pobj] |
||||
dict set result $inout [string tolower [dict get $rec name]] $rec |
||||
} |
||||
} |
||||
} |
||||
|
||||
$mobj -with Qualifiers_ -iterate qual { |
||||
set rec [wmi_extract_qualifier $qual] |
||||
dict set result qualifiers [string tolower [dict get $rec name]] $rec |
||||
$qual destroy |
||||
} |
||||
|
||||
return $result |
||||
} |
||||
|
||||
|
||||
proc twapi::wmi_extract_class {obj} { |
||||
|
||||
set result [dict create] |
||||
|
||||
# Class qualifiers |
||||
$obj -with Qualifiers_ -iterate -cleanup qualobj { |
||||
set rec [wmi_extract_qualifier $qualobj] |
||||
dict set result qualifiers [string tolower [dict get $rec name]] $rec |
||||
} |
||||
|
||||
$obj -with Properties_ -iterate -cleanup propobj { |
||||
set rec [wmi_extract_property $propobj] |
||||
dict set result properties [string tolower [dict get $rec name]] $rec |
||||
} |
||||
|
||||
$obj -with SystemProperties_ -iterate -cleanup propobj { |
||||
set rec [wmi_extract_systemproperty $propobj] |
||||
dict set result systemproperties [string tolower [dict get $rec name]] $rec |
||||
} |
||||
|
||||
$obj -with Methods_ -iterate -cleanup mobj { |
||||
set rec [wmi_extract_method $mobj] |
||||
dict set result methods [string tolower [dict get $rec name]] $rec |
||||
} |
||||
|
||||
return $result |
||||
} |
@ -0,0 +1,9 @@
|
||||
|
||||
package require starkit |
||||
starkit::startup |
||||
if {[llength $::argv]} { |
||||
package require app-shellspy |
||||
} else { |
||||
package require app-punk |
||||
} |
||||
|
Binary file not shown.
@ -0,0 +1,127 @@
|
||||
proc tclInit {} { |
||||
rename tclInit {} |
||||
|
||||
global auto_path tcl_library tcl_libPath tcl_version tclkit_system_encoding |
||||
|
||||
# find the file to mount. |
||||
set noe $::tcl::kitpath |
||||
# resolve symlinks |
||||
set noe [file dirname [file normalize [file join $noe __dummy__]]] |
||||
set tcl_library [file join $noe lib tcl$tcl_version] |
||||
set tcl_libPath [list $tcl_library [file join $noe lib]] |
||||
|
||||
# get rid of a build residue |
||||
unset -nocomplain ::tclDefaultLibrary |
||||
|
||||
# The following code only gets executed if we don't have our exe |
||||
# already mounted. This should only happen once per thread. |
||||
# We could use [vfs::filesystem info], but that would require |
||||
# loading vfs into every interp. |
||||
if {![file isdirectory $noe]} { |
||||
load {} vfs |
||||
|
||||
# lookup and emulate "source" of lib/vfs1*/{vfs*.tcl,mk4vfs.tcl} |
||||
if {[llength [info command mk::file]]} { |
||||
set driver mk4 |
||||
|
||||
# must use raw Metakit calls because VFS is not yet in place |
||||
set d [mk::select exe.dirs parent 0 name lib] |
||||
set d [mk::select exe.dirs parent $d -glob name vfs1*] |
||||
|
||||
foreach x {vfsUtils vfslib mk4vfs} { |
||||
set n [mk::select exe.dirs!$d.files name $x.tcl] |
||||
if {[llength $n] != 1} { error "$x: cannot find startup script"} |
||||
|
||||
set s [mk::get exe.dirs!$d.files!$n contents] |
||||
catch {set s [zlib decompress $s]} |
||||
uplevel #0 $s |
||||
} |
||||
|
||||
# use on-the-fly decompression, if mk4vfs understands that |
||||
# Note: 8.6 core zlib does not support this for mk4vfs |
||||
if {![package vsatisfies [package require Tcl] 8.6]} { |
||||
set mk4vfs::zstreamed 1 |
||||
} |
||||
} else { |
||||
set driver mkcl |
||||
|
||||
# use raw Vlerq calls if Mk4tcl is not available |
||||
# $::vlerq::starkit_root is set in the init script in kitInit.c |
||||
set rootv [vlerq get $::vlerq::starkit_root 0 dirs] |
||||
set dname [vlerq get $rootv * name] |
||||
set prows [vlerq get $rootv * parent] |
||||
foreach r [lsearch -int -all $prows 0] { |
||||
if {[lindex $dname $r] eq "lib"} break |
||||
} |
||||
|
||||
# glob for a subdir in "lib", then source the specified file inside it |
||||
foreach {d f} { |
||||
vfs1* vfsUtils.tcl vfs1* vfslib.tcl vqtcl4* mkclvfs.tcl |
||||
} { |
||||
foreach z [lsearch -int -all $prows $r] { |
||||
if {[string match $d [lindex $dname $z]]} break |
||||
} |
||||
|
||||
set files [vlerq get $rootv $z files] |
||||
set names [vlerq get $files * name] |
||||
|
||||
set n [lsearch $names $f] |
||||
if {$n < 0} { error "$d/$f: cannot find startup script"} |
||||
|
||||
set s [vlerq get $files $n contents] |
||||
catch {set s [zlib decompress $s]} |
||||
uplevel #0 $s |
||||
} |
||||
|
||||
# hack the mkcl info so it will know this mount point as "exe" |
||||
set vfs::mkcl::v::rootv(exe) $rootv |
||||
set vfs::mkcl::v::dname(exe) $dname |
||||
set vfs::mkcl::v::prows(exe) $prows |
||||
} |
||||
|
||||
# mount the executable, i.e. make all runtime files available |
||||
vfs::filesystem mount $noe [list ::vfs::${driver}::handler exe] |
||||
|
||||
# alter path to find encodings |
||||
if {[info tclversion] eq "8.4"} { |
||||
load {} pwb |
||||
librarypath [info library] |
||||
} else { |
||||
encoding dirs [list [file join [info library] encoding]] ;# TIP 258 |
||||
} |
||||
# if the C code passed us a system encoding, apply it here. |
||||
if {[info exists tclkit_system_encoding]} { |
||||
# It is possible the chosen encoding is unavailable in which case |
||||
# we will be left with 'identity' to be handled below. |
||||
catch {encoding system $tclkit_system_encoding} |
||||
unset tclkit_system_encoding |
||||
} |
||||
# fix system encoding, if it wasn't properly set up (200207.004 bug) |
||||
if {[encoding system] eq "identity"} { |
||||
switch $::tcl_platform(platform) { |
||||
windows { encoding system cp1252 } |
||||
macintosh { encoding system macRoman } |
||||
default { encoding system iso8859-1 } |
||||
} |
||||
} |
||||
|
||||
# now remount the executable with the correct encoding |
||||
vfs::filesystem unmount $noe |
||||
set noe $::tcl::kitpath |
||||
# resolve symlinks |
||||
set noe [file dirname [file normalize [file join $noe __dummy__]]] |
||||
|
||||
set tcl_library [file join $noe lib tcl$tcl_version] |
||||
set tcl_libPath [list $tcl_library [file join $noe lib]] |
||||
vfs::filesystem mount $noe [list ::vfs::${driver}::handler exe] |
||||
} |
||||
|
||||
# load config settings file if present |
||||
namespace eval ::vfs { variable tclkit_version 1 } |
||||
catch { uplevel #0 [list source [file join $noe config.tcl]] } |
||||
|
||||
uplevel #0 [list source [file join $tcl_library init.tcl]] |
||||
|
||||
# reset auto_path, so that init.tcl's search outside of tclkit is cancelled |
||||
set auto_path $tcl_libPath |
||||
} |
Binary file not shown.
@ -0,0 +1,333 @@
|
||||
# |
||||
# Ffidl interface to Tcl8.2 |
||||
# |
||||
# Run time support for Ffidl. |
||||
# |
||||
# NOTE: Remember to update FFIDLRT_VERSION in configure.ac when changing this |
||||
# version number. |
||||
package provide Ffidlrt 0.4 |
||||
package require Ffidl |
||||
|
||||
namespace eval ::ffidl:: {} |
||||
|
||||
proc ::ffidl::find-pkg-lib {pkg} { |
||||
package require $pkg |
||||
foreach i [::info loaded {}] { |
||||
foreach {l p} $i {} |
||||
if {$p eq "$pkg"} { |
||||
return $l |
||||
} |
||||
} |
||||
# ignore errors when running under pkg_mkIndex: |
||||
if {![llength [info commands __package_orig]] } { |
||||
return -code error "Library for package $pkg not found" |
||||
} |
||||
} |
||||
|
||||
namespace eval ::ffidl:: { |
||||
set ffidl_lib [find-pkg-lib Ffidl] |
||||
array set libs [list ffidl [list $ffidl_lib] ffidl_test [list $ffidl_lib]] |
||||
unset ffidl_lib |
||||
|
||||
# 'libs' array is used by the ::ffidl::find-lib |
||||
# abstraction to store the resolved lib paths |
||||
# |
||||
# 'types' and 'typedefs' arrays are used by the ::ffidl::find-type |
||||
# abstraction to store resolved system types |
||||
# and whether they have already been defined |
||||
# with ::ffidl::typedef |
||||
array set typedefs {} |
||||
switch -exact $tcl_platform(platform) { |
||||
unix { |
||||
switch -glob $tcl_platform(os) { |
||||
Darwin { |
||||
array set libs { |
||||
c System.framework/System |
||||
m System.framework/System |
||||
gdbm {} |
||||
gmp {} |
||||
mathswig libmathswig0.5.dylib |
||||
} |
||||
array set types { |
||||
size_t {{unsigned long}} |
||||
clock_t {{unsigned long}} |
||||
time_t long |
||||
timeval {uint32 uint32} |
||||
} |
||||
} |
||||
Linux { |
||||
if {$tcl_platform(wordSize) == 8} { |
||||
if {$tcl_platform(machine) eq "alpha"} { |
||||
array set libs { |
||||
c /lib/libc.so.6.1 |
||||
m /lib/libm.so.6.1 |
||||
gdbm /usr/lib/libgdbm.so |
||||
gmp {/usr/local/lib/libgmp.so /usr/lib/libgmp.so.2} |
||||
mathswig libmathswig0.5.so |
||||
} |
||||
array set types { |
||||
size_t long |
||||
clock_t long |
||||
time_t long |
||||
timeval {time_t time_t} |
||||
} |
||||
} else { |
||||
array set libs { |
||||
c { |
||||
/lib64/libc.so.6 |
||||
/lib/x86_64-linux-gnu/libc.so.6 |
||||
} |
||||
m { |
||||
/lib64/libm.so.6 |
||||
/lib/x86_64-linux-gnu/libm.so.6 |
||||
} |
||||
gdbm { |
||||
/usr/lib64/libgdbm.so |
||||
/usr/lib/x86_64-linux-gnu/libgdbm.so |
||||
} |
||||
gmp { |
||||
/usr/lib/x86_64-linux-gnu/libgmp.so |
||||
/usr/local/lib64/libgmp.so |
||||
/usr/lib64/libgmp.so.2 |
||||
} |
||||
mathswig libmathswig0.5.so |
||||
} |
||||
array set types { |
||||
size_t long |
||||
clock_t long |
||||
time_t long |
||||
timeval {time_t time_t} |
||||
} |
||||
} |
||||
} else { |
||||
array set libs { |
||||
c { |
||||
/lib/libc.so.6 |
||||
/lib/i386-linux-gnu/libc.so.6 |
||||
} |
||||
m { |
||||
/lib/libm.so.6 |
||||
/lib/i386-linux-gnu/libm.so.6 |
||||
} |
||||
gdbm { |
||||
/usr/lib/libgdbm.so |
||||
/usr/lib/i386-linux-gnu/libgdbm.so.3 |
||||
} |
||||
gmp { |
||||
/usr/lib/i386-linux-gnu/libgmp.so.2 |
||||
/usr/local/lib/libgmp.so |
||||
/usr/lib/libgmp.so.2 |
||||
} |
||||
mathswig libmathswig0.5.so |
||||
} |
||||
array set types { |
||||
size_t int |
||||
clock_t long |
||||
time_t long |
||||
timeval {time_t time_t} |
||||
} |
||||
} |
||||
} |
||||
*BSD { |
||||
array set libs { |
||||
c {/usr/lib/libc.so /usr/lib/libc.so.30.1} |
||||
m {/usr/lib/libm.so /usr/lib/libm.so.1.0} |
||||
gdbm libgdbm.so |
||||
gmp libgmp.so |
||||
mathswig libmathswig0.5.so |
||||
} |
||||
array set types { |
||||
size_t int |
||||
clock_t long |
||||
time_t long |
||||
timeval {time_t time_t} |
||||
} |
||||
} |
||||
default { |
||||
array set libs { |
||||
c /lib/libc.so |
||||
m /lib/libm.so |
||||
gdbm libgdbm.so |
||||
gmp libgmp.so |
||||
mathswig libmathswig0.5.so |
||||
} |
||||
array set types { |
||||
size_t int |
||||
clock_t long |
||||
time_t long |
||||
timeval {time_t time_t} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
windows { |
||||
# |
||||
# found libraries |
||||
# this array is used by the ::ffidl::find-lib |
||||
# abstraction to store the resolved lib paths |
||||
# |
||||
# CHANGE - put your resolved lib paths here |
||||
# |
||||
array set libs { |
||||
c msvcrt.dll |
||||
m msvcrt.dll |
||||
gdbm {} |
||||
gmp gmp202.dll |
||||
mathswig mathswig05.dll |
||||
} |
||||
# |
||||
# found types |
||||
# these arrays are used by the ::ffidl::find-type |
||||
# abstraction to store resolved system types |
||||
# and whether they have already been defined |
||||
# with ::ffidl::typedef |
||||
# |
||||
# CHANGE - put your resolved system types here |
||||
# |
||||
array set types { |
||||
size_t int |
||||
clock_t long |
||||
time_t long |
||||
timeval {time_t time_t} |
||||
} |
||||
array set typedefs { |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
# |
||||
# find a shared library given a root name |
||||
# this is an abstraction in search of a |
||||
# solution. |
||||
# |
||||
# currently wired for my linux box |
||||
# |
||||
proc ::ffidl::find-lib {root} { |
||||
upvar \#0 ::ffidl::libs libs |
||||
if { ! [::info exists libs($root)] || [llength libs($root)] == 0} { |
||||
error "::ffidl::find-lib $root - no mapping defined for $root" |
||||
} |
||||
if {[llength $libs($root)] > 1} { |
||||
foreach l $libs($root) { |
||||
if {[file exists $l]} { |
||||
set libs($root) $l |
||||
break |
||||
} |
||||
} |
||||
} |
||||
lindex $libs($root) 0 |
||||
} |
||||
|
||||
# |
||||
# find a typedef for a standard type |
||||
# and define it with ::ffidl::typedef |
||||
# if not already done |
||||
# |
||||
# currently wired for my linux box |
||||
# |
||||
proc ::ffidl::find-type {type} { |
||||
upvar \#0 ::ffidl::types types |
||||
upvar \#0 ::ffidl::typedefs typedefs |
||||
if { ! [::info exists types($type)]} { |
||||
error "::ffidl::find-type $type - no mapping defined for $type" |
||||
} |
||||
if { ! [::info exists typedefs($type)]} { |
||||
eval ::ffidl::typedef $type $types($type) |
||||
set typedefs($type) 1 |
||||
} |
||||
} |
||||
|
||||
# |
||||
# get the address of the string rep of a Tcl_Obj |
||||
# get the address of the unicode rep of a Tcl_Obj |
||||
# get the address of the bytearray rep of a Tcl_Obj |
||||
# |
||||
# CAUTION - anything which alters the Tcl_Obj may |
||||
# invalidate the results of this function. Use |
||||
# only in circumstances where the Tcl_Obj will not |
||||
# be modified in any way. |
||||
# |
||||
# CAUTION - the memory pointed to by the addresses |
||||
# returned by ::ffidl::get-string and ::ffidl::get-unicode |
||||
# is managed by Tcl, the contents should never be |
||||
# modified. |
||||
# |
||||
# The memory pointed to by ::ffidl::get-bytearray may |
||||
# be modified if care is taken to respect its size, |
||||
# and if shared references to the bytearray object |
||||
# are known to be compatible with the modification. |
||||
# |
||||
|
||||
::ffidl::callout ::ffidl::get-string {pointer-obj} pointer [::ffidl::stubsymbol tcl stubs 340]; #Tcl_GetString |
||||
::ffidl::callout ::ffidl::get-unicode {pointer-obj} pointer [::ffidl::stubsymbol tcl stubs 382]; #Tcl_GetUnicode |
||||
::ffidl::callout ::ffidl::get-bytearray-from-obj {pointer-obj pointer-var} pointer [::ffidl::stubsymbol tcl stubs 33]; #Tcl_GetByteArrayFromObj |
||||
|
||||
proc ::ffidl::get-bytearray {obj} { |
||||
set len [binary format [::ffidl::info format int] 0] |
||||
::ffidl::get-bytearray-from-obj $obj len |
||||
} |
||||
|
||||
# |
||||
# create a new string Tcl_Obj |
||||
# create a new unicode Tcl_Obj |
||||
# create a new bytearray Tcl_Obj |
||||
# |
||||
# I'm not sure if these are actually useful |
||||
# |
||||
|
||||
::ffidl::callout ::ffidl::new-string {pointer int} pointer-obj [::ffidl::stubsymbol tcl stubs 56]; #Tcl_NewStringObj |
||||
::ffidl::callout ::ffidl::new-unicode {pointer int} pointer-obj [::ffidl::stubsymbol tcl stubs 378]; #Tcl_NewUnicodeObj |
||||
::ffidl::callout ::ffidl::new-bytearray {pointer int} pointer-obj [::ffidl::stubsymbol tcl stubs 50]; #Tcl_NewByteArrayObj |
||||
|
||||
::ffidl::find-type size_t |
||||
if {1} { |
||||
# Tcl's allocator: malloc, free, realloc. |
||||
::ffidl::callout ::ffidl::malloc {unsigned} pointer [::ffidl::stubsymbol tcl stubs 3]; #Tcl_Alloc |
||||
::ffidl::callout ::ffidl::realloc {pointer unsigned} pointer [::ffidl::stubsymbol tcl stubs 5]; #Tcl_Realloc |
||||
::ffidl::callout ::ffidl::free {pointer} void [::ffidl::stubsymbol tcl stubs 4]; #Tcl_Free |
||||
} else { |
||||
# access the standard allocator: malloc, free, realloc. |
||||
::ffidl::callout ::ffidl::malloc {size_t} pointer [::ffidl::symbol [::ffidl::find-lib c] malloc] |
||||
::ffidl::callout ::ffidl::realloc {pointer size_t} pointer [::ffidl::symbol [::ffidl::find-lib c] realloc] |
||||
::ffidl::callout ::ffidl::free {pointer} void [::ffidl::symbol [::ffidl::find-lib c] free] |
||||
} |
||||
|
||||
# |
||||
# Copy some memory at some location into a Tcl bytearray. |
||||
# |
||||
# Needless to say, this can be very hazardous to your |
||||
# program's health if things aren't sized correctly. |
||||
# |
||||
::ffidl::callout ::ffidl::memcpy {pointer-var pointer size_t} pointer [::ffidl::symbol [::ffidl::find-lib ffidl] ffidl_copy_bytes]; |
||||
|
||||
# |
||||
# Regular memcpy working on pointers. ::ffidl::memcpy kept as is for compatibilitiy. |
||||
# |
||||
::ffidl::callout ::ffidl::memcpy2 {pointer pointer size_t} pointer [::ffidl::symbol [::ffidl::find-lib ffidl] ffidl_copy_bytes]; |
||||
|
||||
# |
||||
# Create a Tcl bytearray with a copy of the contents some memory location. |
||||
# |
||||
proc ::ffidl::peek {address nbytes} { |
||||
set dst [binary format x$nbytes] |
||||
::ffidl::memcpy dst $address $nbytes |
||||
set dst |
||||
} |
||||
|
||||
# |
||||
# Copy the contents of a Tcl bytearray to some memory location. |
||||
# |
||||
proc ::ffidl::poke {dst src} { |
||||
set n [string length $bytes]; |
||||
set src [::ffidl::get-bytearray $bytes]; |
||||
::ffidl::memcpy2 $dst $src $n; |
||||
} |
||||
|
||||
# |
||||
# convert raw pointers, as integers, into Tcl_Obj's |
||||
# |
||||
::ffidl::callout ::ffidl::pointer-into-string {pointer} pointer-utf8 [::ffidl::symbol [::ffidl::find-lib ffidl] ffidl_pointer_pun] |
||||
::ffidl::callout ::ffidl::pointer-into-unicode {pointer} pointer-utf16 [::ffidl::symbol [::ffidl::find-lib ffidl] ffidl_pointer_pun] |
||||
# ::ffidl::pointer-into-bytearray is deprecated. Use ::ffidl::peek instead. |
||||
interp alias {} ::ffidl::pointer-into-bytearray {} ::ffidl::peek; |
@ -0,0 +1,12 @@
|
||||
# Tcl package index file, version 1.1 |
||||
# This file is generated by the "pkg_mkIndex" command |
||||
# and sourced either when an application starts up or |
||||
# by a "package unknown" script. It invokes the |
||||
# "package ifneeded" command to set up package-related |
||||
# information so that packages will be loaded automatically |
||||
# in response to "package require" commands. When this |
||||
# script is sourced, the variable $dir must contain the |
||||
# full path name of this file's directory. |
||||
|
||||
package ifneeded Ffidl 0.9.0 [list load [file join $dir Ffidl090.dll]] |
||||
package ifneeded Ffidlrt 0.4 [list source [file join $dir ffidlrt.tcl]] |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
package ifneeded Memchan 2.3 \ |
||||
[list load [file join $dir Memchan23.dll]] |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
package ifneeded Trf 2.1.4 \ |
||||
[list load [file join $dir Trf214.dll]] |
@ -0,0 +1,29 @@
|
||||
# |
||||
# Tcl package index file - generated from pkgIndex.tcl.in |
||||
# |
||||
|
||||
package ifneeded cffi 1.2.0 \ |
||||
[list apply [list {dir} { |
||||
set dllname "tclcffi120.dll" |
||||
set package "cffi" |
||||
set package_ns ::$package |
||||
|
||||
# First try to load from current directory. If that fails, try from |
||||
# arch-specific subdirectories |
||||
set path [file join $dir $dllname] |
||||
if {[catch {uplevel #0 [list load $path $package]}]} { |
||||
package require platform |
||||
foreach platform [platform::patterns [platform::identify]] { |
||||
if {$platform eq "tcl"} continue |
||||
set path [file join $dir $platform $dllname] |
||||
if {![catch {uplevel #0 [list load $path $package]}]} { |
||||
break |
||||
} |
||||
} |
||||
} |
||||
if {[namespace exists $package_ns]} { |
||||
# Load was successful |
||||
set ${package_ns}::dll_path $path |
||||
set ${package_ns}::package_dir $dir |
||||
} |
||||
}] $dir] |
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,2 @@
|
||||
if {![package vsatisfies [package provide Tcl] 8.6]} {return} |
||||
package ifneeded critcl::app 3.2 [list source [file join $dir critcl.tcl]] |
@ -0,0 +1,134 @@
|
||||
# |
||||
# Critcl - build C extensions on-the-fly |
||||
# |
||||
# Copyright (c) 2001-2007 Jean-Claude Wippler |
||||
# Copyright (c) 2002-2007 Steve Landers |
||||
# |
||||
# See http://wiki.tcl.tk/critcl |
||||
# |
||||
# This is the Critcl runtime that loads the appropriate |
||||
# shared library when a package is requested |
||||
# |
||||
|
||||
namespace eval ::critcl::runtime {} |
||||
|
||||
proc ::critcl::runtime::loadlib {dir package version libname initfun tsrc mapping args} { |
||||
# XXX At least parts of this can be done by the package generator, |
||||
# XXX like listing the Tcl files to source. The glob here allows |
||||
# XXX code-injection after-the-fact, by simply adding a .tcl in |
||||
# XXX the proper place. |
||||
set path [file join $dir [MapPlatform $mapping]] |
||||
set ext [info sharedlibextension] |
||||
set lib [file join $path $libname$ext] |
||||
set provide [list] |
||||
|
||||
# Now the runtime equivalent of a series of 'preFetch' commands. |
||||
if {[llength $args]} { |
||||
set preload [file join $path preload$ext] |
||||
foreach p $args { |
||||
set prelib [file join $path $p$ext] |
||||
if {[file readable $preload] && [file readable $prelib]} { |
||||
lappend provide [list load $preload];# XXX Move this out of the loop, do only once. |
||||
lappend provide [list ::critcl::runtime::preload $prelib] |
||||
} |
||||
} |
||||
} |
||||
|
||||
lappend provide [list load $lib $initfun] |
||||
foreach t $tsrc { |
||||
lappend loadcmd "::critcl::runtime::Fetch \$dir [list $t]" |
||||
} |
||||
lappend provide "package provide $package $version" |
||||
package ifneeded $package $version [join $provide "\n"] |
||||
return |
||||
} |
||||
|
||||
proc ::critcl::runtime::preFetch {path ext dll} { |
||||
set preload [file join $path preload$ext] |
||||
if {![file readable $preload]} return |
||||
|
||||
set prelib [file join $path $dll$ext] |
||||
if {![file readable $prelib]} return |
||||
|
||||
load $preload ; # Defines next command. |
||||
::critcl::runtime::preload $prelib |
||||
return |
||||
} |
||||
|
||||
proc ::critcl::runtime::Fetch {dir t} { |
||||
# The 'Ignore' disables compile & run functionality. |
||||
|
||||
# Background: If the regular critcl package is already loaded, and |
||||
# this prebuilt package uses its defining .tcl file also as a |
||||
# 'tsources' then critcl might try to collect data and build it |
||||
# because of the calls to its API, despite the necessary binaries |
||||
# already being present, just not in the critcl cache. That is |
||||
# redundant in the best case, and fails in the worst case (no |
||||
# compiler), preventing the use o a perfectly fine package. The |
||||
# 'ignore' call now tells critcl that it should ignore any calls |
||||
# made to it by the sourced files, and thus avoids that trouble. |
||||
|
||||
# The other case, the regular critcl package getting loaded after |
||||
# this prebuilt package is irrelevant. At that point the tsources |
||||
# were already run, and used the dummy procedures defined in the |
||||
# critcl-rt.tcl, which ignore the calls by definition. |
||||
|
||||
set t [file join $dir tcl $t] |
||||
::critcl::Ignore $t |
||||
uplevel #0 [list source $t] |
||||
return |
||||
} |
||||
|
||||
proc ::critcl::runtime::precopy {dll} { |
||||
# This command is only used on Windows when preloading out of a |
||||
# VFS that doesn't support direct loading (usually, a Starkit) |
||||
# - we preserve the dll name so that dependencies are satisfied |
||||
# - The critcl::runtime::preload command is defined in the supporting |
||||
# "preload" package, implemented in "critcl/lib/critcl/critcl_c/preload.c" |
||||
|
||||
global env |
||||
if {[info exists env(TEMP)]} { |
||||
set dir $env(TEMP) |
||||
} elseif {[info exists env(TMP)]} { |
||||
set dir $env(TMP) |
||||
} elseif {[info exists ~]} { |
||||
set dir ~ |
||||
} else { |
||||
set dir . |
||||
} |
||||
set dir [file join $dir TCL[pid]] |
||||
set i 0 |
||||
while {[file exists $dir]} { |
||||
append dir [incr i] |
||||
} |
||||
set new [file join $dir [file tail $dll]] |
||||
file mkdir $dir |
||||
file copy $dll $new |
||||
return $new |
||||
} |
||||
|
||||
proc ::critcl::runtime::MapPlatform {{mapping {}}} { |
||||
# A sibling of critcl::platform that applies the platform mapping |
||||
|
||||
set platform [::platform::generic] |
||||
set version $::tcl_platform(osVersion) |
||||
if {[string match "macosx-*" $platform]} { |
||||
# "normalize" the osVersion to match OSX release numbers |
||||
set v [split $version .] |
||||
set v1 [lindex $v 0] |
||||
set v2 [lindex $v 1] |
||||
incr v1 -4 |
||||
set version 10.$v1.$v2 |
||||
} else { |
||||
# Strip trailing non-version info |
||||
regsub -- {-.*$} $version {} version |
||||
} |
||||
foreach {config map} $mapping { |
||||
if {![string match $config $platform]} continue |
||||
set minver [lindex $map 1] |
||||
if {[package vcompare $version $minver] < 0} continue |
||||
set platform [lindex $map 0] |
||||
break |
||||
} |
||||
return $platform |
||||
} |
@ -0,0 +1,188 @@
|
||||
## -*- tcl -*- Critcl configuration file |
||||
# # ## ### ##### ######## ############# ##################### |
||||
## For |
||||
# @@PNAME@@ @@PMAJORV@@.@@PMINORV@@ |
||||
# |
||||
# Copyright (c) @@YEAR@@ @@PORG@@ |
||||
# |
||||
# Generated by @@CRITCL@@ |
||||
# At @@NOW@@ |
||||
|
||||
# This specific file gets filled by the TEA configure(.in) with the |
||||
# compiler information it found when run, and the accompanying |
||||
# Makefile(.in) uses it to overide critcl's default configuration |
||||
# settings. In this way we manage to get a proper TEA setup of flags |
||||
# and such, bypassing all of critcl's own selection logic. critcl is |
||||
# essentially 'just' used as a custom compiler driver, whereas a |
||||
# standard TEA Makefile would have all the relevant commands listed |
||||
# explicitly in its rules. |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## First, keep the GCC specific defaults. |
||||
|
||||
compile gcc -c -fPIC |
||||
version gcc -v |
||||
link gcc -shared |
||||
include -I |
||||
preproc_define gcc -E -dM |
||||
preproc_enum gcc -E |
||||
tclstubs -DUSE_TCL_STUBS |
||||
tkstubs -DUSE_TK_STUBS |
||||
debug_memory -DTCL_MEM_DEBUG |
||||
debug_symbols -g |
||||
object .o |
||||
output -o [list $outfile] |
||||
ldoutput |
||||
link_debug |
||||
link_release |
||||
link_preload --unresolved-symbols=ignore-in-shared-libs |
||||
strip -Wl,-s |
||||
optimize -O2 |
||||
noassert -DNDEBUG |
||||
threadflags -DUSE_THREAD_ALLOC=1 -D_REENTRANT=1 -D_THREAD_SAFE=1 \ |
||||
-DHAVE_PTHREAD_ATTR_SETSTACKSIZE=1 -DHAVE_READDIR_R=1 \ |
||||
-DTCL_THREADS=1 |
||||
|
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Second, define settings based on the system information found by |
||||
## configure(.in), converted into something usable by critcl. See the |
||||
## section below for the raw settings. |
||||
|
||||
TEA platform @CRITCL_PLATFORM@ |
||||
TEA compile @CRITCL_CC@ |
||||
TEA version @CRITCL_VERSION@ |
||||
TEA link @CRITCL_LINK@ |
||||
TEA preproc_define @CRITCL_CPP_DEFINE@ |
||||
TEA preproc_enum @CRITCL_CPP_ENUM@ |
||||
TEA debug_symbols @CFLAGS_DEBUG@ |
||||
TEA object .@OBJEXT@ |
||||
TEA output @CRITCL_CC_OUTPUT@ |
||||
TEA ldoutput @CRITCL_LD_OUTPUT@ |
||||
TEA link_debug @CRITCL_LD_DBG@ |
||||
TEA link_release @CRITCL_LD_REL@ |
||||
TEA link_preload --unresolved-symbols=ignore-in-shared-libs |
||||
TEA strip |
||||
TEA optimize @CFLAGS_OPTIMIZE@ |
||||
|
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Third, the exact raw settings generated by configure(.in), |
||||
## as found in build_dir/config.status. To help debugging the |
||||
## munging, when its wrong. |
||||
## |
||||
## The lines marked with ** are those which are of especially high |
||||
## interest. |
||||
|
||||
#** CC = (@CC@) |
||||
#** CFLAGS = (@CFLAGS@) |
||||
#** CFLAGS_DEBUG = (@CFLAGS_DEBUG@) |
||||
#** CFLAGS_OPTIMIZE = (@CFLAGS_OPTIMIZE@) |
||||
#** CFLAGS_WARNING = (@CFLAGS_WARNING@) |
||||
#** CPP = (@CPP@) |
||||
#** CPPFLAGS = (@CPPFLAGS@) |
||||
#** DEFS = (@DEFS@) |
||||
#** LDFLAGS = (@LDFLAGS@) |
||||
#** LDFLAGS_DEFAULT = (@LDFLAGS_DEFAULT@) |
||||
#** LIBS = (@LIBS@) |
||||
#** MAKE_LIB = (@MAKE_LIB@) |
||||
#** MAKE_SHARED_LIB = (@MAKE_SHARED_LIB@) |
||||
#** MAKE_STATIC_LIB = (@MAKE_STATIC_LIB@) |
||||
#** MAKE_STUB_LIB = (@MAKE_STUB_LIB@) |
||||
#** MATH_LIBS = (@MATH_LIBS@) |
||||
#** OBJEXT = (@OBJEXT@) |
||||
#** SHLIB_CFLAGS = (@SHLIB_CFLAGS@) |
||||
#** SHLIB_LD = (@SHLIB_LD@) |
||||
#** SHLIB_LD_LIBS = (@SHLIB_LD_LIBS@) |
||||
#** SHLIB_SUFFIX = (@SHLIB_SUFFIX@) |
||||
#** STLIB_LD = (@STLIB_LD@) |
||||
#** TCL_EXTRA_CFLAGS = (@TCL_EXTRA_CFLAGS@) |
||||
#** TCL_INCLUDES = (@TCL_INCLUDES@) |
||||
#** TCL_LD_FLAGS = (@TCL_LD_FLAGS@) |
||||
#** TCL_LIBS = (@TCL_LIBS@) |
||||
#** TCL_SHLIB_LD_LIBS = (@TCL_SHLIB_LD_LIBS@) |
||||
#** TCL_THREADS = (@TCL_THREADS@) |
||||
|
||||
# AR = (@AR@) |
||||
# CELIB_DIR = (@CELIB_DIR@) |
||||
# CFLAGS_DEFAULT = (@CFLAGS_DEFAULT@) |
||||
# CLEANFILES = (@CLEANFILES@) |
||||
# CYGPATH = (@CYGPATH@) |
||||
# ECHO_C = (@ECHO_C@) |
||||
# ECHO_N = (@ECHO_N@) |
||||
# ECHO_T = (@ECHO_T@) |
||||
# EGREP = (@EGREP@) |
||||
# EXEEXT = (@EXEEXT@) |
||||
# GREP = (@GREP@) |
||||
# INSTALL_DATA = (@INSTALL_DATA@) |
||||
# INSTALL_PROGRAM = (@INSTALL_PROGRAM@) |
||||
# INSTALL_SCRIPT = (@INSTALL_SCRIPT@) |
||||
# LD_LIBRARY_PATH_VAR = (@LD_LIBRARY_PATH_VAR@) |
||||
# LIBOBJS = (@LIBOBJS@) |
||||
# LTLIBOBJS = (@LTLIBOBJS@) |
||||
# PACKAGE_BUGREPORT = (@PACKAGE_BUGREPORT@) |
||||
# PACKAGE_NAME = (@PACKAGE_NAME@) |
||||
# PACKAGE_STRING = (@PACKAGE_STRING@) |
||||
# PACKAGE_TARNAME = (@PACKAGE_TARNAME@) |
||||
# PACKAGE_VERSION = (@PACKAGE_VERSION@) |
||||
# PATH_SEPARATOR = (@PATH_SEPARATOR@) |
||||
# PKG_CFLAGS = (@PKG_CFLAGS@) |
||||
# PKG_HEADERS = (@PKG_HEADERS@) |
||||
# PKG_INCLUDES = (@PKG_INCLUDES@) |
||||
# PKG_LIBS = (@PKG_LIBS@) |
||||
# PKG_LIB_FILE = (@PKG_LIB_FILE@) |
||||
# PKG_OBJECTS = (@PKG_OBJECTS@) |
||||
# PKG_SOURCES = (@PKG_SOURCES@) |
||||
# PKG_STUB_LIB_FILE = (@PKG_STUB_LIB_FILE@) |
||||
# PKG_STUB_OBJECTS = (@PKG_STUB_OBJECTS@) |
||||
# PKG_STUB_SOURCES = (@PKG_STUB_SOURCES@) |
||||
# PKG_TCL_SOURCES = (@PKG_TCL_SOURCES@) |
||||
# RANLIB = (@RANLIB@) |
||||
# RANLIB_STUB = (@RANLIB_STUB@) |
||||
# SET_MAKE = (@SET_MAKE@) |
||||
# SHARED_BUILD = (@SHARED_BUILD@) |
||||
# SHELL = (@SHELL@) |
||||
# TCLSH_PROG = (@TCLSH_PROG@) |
||||
# TCL_BIN_DIR = (@TCL_BIN_DIR@) |
||||
# TCL_DBGX = (@TCL_DBGX@) |
||||
# TCL_DEFS = (@TCL_DEFS@) |
||||
# TCL_LIB_FILE = (@TCL_LIB_FILE@) |
||||
# TCL_LIB_FLAG = (@TCL_LIB_FLAG@) |
||||
# TCL_LIB_SPEC = (@TCL_LIB_SPEC@) |
||||
# TCL_PATCH_LEVEL = (@TCL_PATCH_LEVEL@) |
||||
# TCL_SRC_DIR = (@TCL_SRC_DIR@) |
||||
# TCL_STUB_LIB_FILE = (@TCL_STUB_LIB_FILE@) |
||||
# TCL_STUB_LIB_FLAG = (@TCL_STUB_LIB_FLAG@) |
||||
# TCL_STUB_LIB_SPEC = (@TCL_STUB_LIB_SPEC@) |
||||
# TCL_VERSION = (@TCL_VERSION@) |
||||
# VC_MANIFEST_EMBED_DLL = (@VC_MANIFEST_EMBED_DLL@) |
||||
# VC_MANIFEST_EMBED_EXE = (@VC_MANIFEST_EMBED_EXE@) |
||||
|
||||
# ac_ct_CC = (@ac_ct_CC@) |
||||
# bindir = (@bindir@) |
||||
# build_alias = (@build_alias@) |
||||
# datadir = (@datadir@) |
||||
# datarootdir = (@datarootdir@) |
||||
# docdir = (@docdir@) |
||||
# dvidir = (@dvidir@) |
||||
# exec_prefix = (@exec_prefix@) |
||||
# host_alias = (@host_alias@) |
||||
# htmldir = (@htmldir@) |
||||
# includedir = (@includedir@) |
||||
# infodir = (@infodir@) |
||||
# libdir = (@libdir@) |
||||
# libexecdir = (@libexecdir@) |
||||
# localedir = (@localedir@) |
||||
# localstatedir = (@localstatedir@) |
||||
# mandir = (@mandir@) |
||||
# oldincludedir = (@oldincludedir@) |
||||
# pdfdir = (@pdfdir@) |
||||
# prefix = (@prefix@) |
||||
# program_transform_name = (@program_transform_name@) |
||||
# psdir = (@psdir@) |
||||
# sbindir = (@sbindir@) |
||||
# sharedstatedir = (@sharedstatedir@) |
||||
# sysconfdir = (@sysconfdir@) |
||||
# target_alias = (@target_alias@) |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
@ -0,0 +1,145 @@
|
||||
# Makefile.in --
|
||||
#
|
||||
# This file is a Makefile for "@@PNAME@@ @@PMAJORV@@.@@PMINORV@@". If this
|
||||
# is "Makefile.in" then it is a template for a Makefile; to generate
|
||||
# the actual Makefile, run "./configure", which is a configuration script
|
||||
# generated by the "autoconf" program (constructs like "@foo@" will get
|
||||
# replaced in the actual Makefile.
|
||||
#
|
||||
# Copyright (c) @@YEAR@@ @@PORG@@
|
||||
#
|
||||
# Generated by @@CRITCL@@
|
||||
# At @@NOW@@
|
||||
#
|
||||
# See the file "license.terms" for information on usage and redistribution
|
||||
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
|
||||
#========================================================================
|
||||
# Nothing of the variables below this line need to be changed. Please
|
||||
# check the TARGETS section below to make sure the make targets are
|
||||
# correct.
|
||||
#========================================================================
|
||||
|
||||
SHELL = @SHELL@
|
||||
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
libdir = @libdir@
|
||||
mandir = @mandir@
|
||||
bindir = @bindir@
|
||||
|
||||
sbindir = @sbindir@
|
||||
libexecdir = @libexecdir@
|
||||
datarootdir = @datarootdir@
|
||||
datadir = @datadir@
|
||||
sysconfdir = @sysconfdir@
|
||||
sharedir = @sharedstatedir@
|
||||
statedir = @localstatedir@
|
||||
includedir = @includedir@
|
||||
oldincludedir = @oldincludedir@
|
||||
|
||||
DESTDIR =
|
||||
pkglibdir = $(libdir)/@PACKAGE_NAME@@PACKAGE_VERSION@
|
||||
top_builddir = .
|
||||
|
||||
PACKAGE = @PACKAGE_NAME@
|
||||
VERSION = @PACKAGE_VERSION@
|
||||
CYGPATH = @CYGPATH@
|
||||
|
||||
TCLSH_PROG = @TCLSH_PROG@
|
||||
CRITCL = `$(CYGPATH) $(srcdir)/critcl/main.tcl`
|
||||
|
||||
CONFIG_CLEAN_FILES =
|
||||
@@API@@ |
||||
@@UCONFIG@@ |
||||
#========================================================================
|
||||
# PKG_TCL_SOURCES identifies Tcl runtime files that are associated with
|
||||
# this package that need to be installed, if any.
|
||||
#========================================================================
|
||||
|
||||
PKG_TCL_SOURCES = @@PFILES@@
|
||||
|
||||
#========================================================================
|
||||
# Start of user-definable TARGETS section
|
||||
#========================================================================
|
||||
|
||||
all: |
||||
@echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
@echo Please run '"make install"' to build and install the package.
|
||||
@echo Critcl has no separate build-step.
|
||||
|
||||
doc: |
||||
@echo No documentation to build.
|
||||
|
||||
install: |
||||
@echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ; \
|
||||
cat $(top_builddir)/Config | grep -v '^#' ; \
|
||||
echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ; \
|
||||
rm -rf $(top_builddir)/results-tea ; \
|
||||
$(TCLSH_PROG) $(CRITCL) \
|
||||
-I $(prefix)/include \
|
||||
-I $(exec_prefix)/include \
|
||||
-I $(includedir)@@APIUSE@@ \
|
||||
@@UCONFIGUSE@@ \
|
||||
-keep -cache $(top_builddir)/results-tea \
|
||||
-target TEA -config $(top_builddir)/Config \
|
||||
-libdir $(DESTDIR)$(libdir) \
|
||||
-includedir $(DESTDIR)$(includedir) \
|
||||
-pkg $(PACKAGE)$(VERSION) \
|
||||
$(PKG_TCL_SOURCES) ; \
|
||||
echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ; \
|
||||
cat $(top_builddir)/results-tea/*.log
|
||||
echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ; \
|
||||
|
||||
install-auto: show-auto |
||||
@rm -rf $(top_builddir)/results-auto ; \
|
||||
$(TCLSH_PROG) $(CRITCL) \
|
||||
-I $(prefix)/include \
|
||||
-I $(exec_prefix)/include \
|
||||
-I $(includedir)@@APIUSE@@ \
|
||||
@@UCONFIGUSE@@ \
|
||||
-keep -cache $(top_builddir)/results-auto \
|
||||
-libdir $(DESTDIR)$(libdir) \
|
||||
-includedir $(DESTDIR)$(includedir) \
|
||||
-pkg $(PACKAGE)$(VERSION) \
|
||||
$(PKG_TCL_SOURCES) ; \
|
||||
echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ; \
|
||||
cat $(top_builddir)/results-auto/*.log
|
||||
echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ; \
|
||||
|
||||
install-doc: |
||||
@echo No documentation to install.
|
||||
|
||||
show: |
||||
@$(TCLSH_PROG) $(CRITCL) \
|
||||
-keep -cache $(top_builddir)/results-tea \
|
||||
-target TEA -config $(top_builddir)/Config \
|
||||
-libdir $(DESTDIR)$(libdir) \
|
||||
-pkg -show
|
||||
|
||||
show-auto: |
||||
@$(TCLSH_PROG) $(CRITCL) \
|
||||
-keep -cache $(top_builddir)/results-auto \
|
||||
-libdir $(DESTDIR)$(libdir) \
|
||||
-pkg -show
|
||||
|
||||
clean: |
||||
rm -rf doc *-doc
|
||||
|
||||
distclean: clean |
||||
-rm -f Makefile $(CONFIG_CLEAN_FILES)
|
||||
-rm -f config.cache config.log stamp-h stamp-h[0-9]*
|
||||
-rm -f config.status
|
||||
|
||||
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status |
||||
cd $(top_builddir) \
|
||||
&& CONFIG_FILES=$@ CONFIG_HEADERS= $(SHELL) ./config.status
|
||||
|
||||
|
||||
.PHONY: all binaries clean depend distclean doc install installdirs libraries test |
||||
|
||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||
.NOEXPORT: |
@ -0,0 +1,180 @@
|
||||
# |
||||
# Include the TEA standard macro set |
||||
# |
||||
|
||||
builtin(include,tclconfig/tcl.m4) |
||||
|
||||
# |
||||
# Add here whatever m4 macros you want to define for your package |
||||
# |
||||
|
||||
#------------------------------------------------------------------------ |
||||
# CRITCL_TEA_PUBLIC_PACKAGE_HEADERS -- |
||||
# |
||||
# Locate the installed public FOO header files |
||||
# |
||||
# Arguments: |
||||
# Name of the package to search headers for. |
||||
# |
||||
# Requires: |
||||
# CYGPATH must be set |
||||
# |
||||
# Results: |
||||
# |
||||
# Adds a --with-[$1]-include switch to configure. |
||||
# Result is cached. |
||||
# |
||||
# Substs the following vars: |
||||
# CRITCL_API_$1_INCLUDE |
||||
#------------------------------------------------------------------------ |
||||
|
||||
AC_DEFUN([CRITCL_TEA_PUBLIC_PACKAGE_HEADERS],[ |
||||
# CRITCL_TEA_PUBLIC_PACKAGE_HEADERS: $1 |
||||
AC_MSG_CHECKING([for $1 public headers]) |
||||
AC_ARG_WITH([$1-include], [ --with-$1-include directory containing the public $1 header files], [with_$1_include=${withval}]) |
||||
AC_CACHE_VAL(ac_cv_c_$1_header, [ |
||||
# Use the value from --with-$1-include, if it was given |
||||
|
||||
if test x"[$]{with_$1_include}" != x ; then |
||||
if test -f "[$]{with_$1_include}/$1Decls.h" ; then |
||||
ac_cv_c_$1_header=[$]{with_$1_include} |
||||
else |
||||
AC_MSG_ERROR([[$]{with_$1_include} directory does not contain $1Decls.h]) |
||||
fi |
||||
else |
||||
list="" |
||||
if test "`uname -s`" = "Darwin"; then |
||||
# If $1 was built as a framework, attempt to use |
||||
# the framework's Headers directory |
||||
case [$]{$1_DEFS} in |
||||
*$1_FRAMEWORK*) |
||||
list="`ls -d [$]{$1_BIN_DIR}/Headers 2>/dev/null`" |
||||
;; |
||||
esac |
||||
fi |
||||
|
||||
# Check order: pkg --prefix location, Tcl's --prefix location, |
||||
# relative to directory of $1Config.sh. |
||||
|
||||
eval "temp_includedir=[$]{includedir}" |
||||
list="[$]list \ |
||||
`ls -d [$]{temp_includedir} 2>/dev/null` \ |
||||
`ls -d [$]{$1_PREFIX}/include 2>/dev/null` \ |
||||
`ls -d [$]{$1_BIN_DIR}/../include 2>/dev/null` \ |
||||
`ls -d ${TCL_PREFIX}/include 2>/dev/null` \ |
||||
`ls -d ${TCL_BIN_DIR}/../include 2>/dev/null`" |
||||
|
||||
if test "[$]{TEA_PLATFORM}" != "windows" -o "[$]GCC" = "yes"; then |
||||
list="[$]list /usr/local/include /usr/include" |
||||
if test x"[$]{$1_INCLUDE_SPEC}" != x ; then |
||||
d=`echo "[$]{$1_INCLUDE_SPEC}" | sed -e 's/^-I//'` |
||||
list="[$]list `ls -d ${d} 2>/dev/null`" |
||||
fi |
||||
fi |
||||
for i in [$]list ; do |
||||
if test -f "[$]i/$1/$1Decls.h" ; then |
||||
ac_cv_c_$1_header=[$]i |
||||
break |
||||
fi |
||||
done |
||||
fi |
||||
]) |
||||
|
||||
# Print a message based on how we determined the include path |
||||
if test x"[$]{ac_cv_c_$1_header}" = x ; then |
||||
AC_MSG_ERROR([$1Decls.h not found. Please specify its location with --with-$1-include]) |
||||
else |
||||
AC_MSG_RESULT([[$]{ac_cv_c_$1_header}]) |
||||
fi |
||||
|
||||
# Convert to a native path and substitute into the transfer variable. |
||||
# NOTE: Anything going into actual TEA would have to use A TEA_xx |
||||
# transfer variable, instead of critcl. |
||||
INCLUDE_DIR_NATIVE=`[$]{CYGPATH} [$]{ac_cv_c_$1_header}` |
||||
CRITCL_API_$1_INCLUDE="\"[$]{INCLUDE_DIR_NATIVE}\"" |
||||
AC_SUBST([CRITCL_API_$1_INCLUDE]) |
||||
]) |
||||
|
||||
#------------------------------------------------------------------------ |
||||
# CRITCL_TEA_WITH_CONFIG -- |
||||
# |
||||
# Declare a --with-FOO option, with default and legal values. |
||||
# |
||||
# Arguments: |
||||
# Name of the option. |
||||
# List of legal values. |
||||
# Default value. |
||||
# Option description. |
||||
# |
||||
# Requires: |
||||
# Results: |
||||
# Adds a --with-[$1] switch to configure. |
||||
# |
||||
# Substs the following vars: |
||||
# CRITCL_UCONFIG_$1 |
||||
#------------------------------------------------------------------------ |
||||
|
||||
AC_DEFUN([CRITCL_TEA_WITH_CONFIG],[ |
||||
# CRITCL_TEA_WITH_CONFIG: $1 |
||||
AC_ARG_WITH([$1], |
||||
AC_HELP_STRING([--with-$1], |
||||
[$4]), |
||||
[with_uc_$1=${withval}]) |
||||
|
||||
# Use default if user did not specify anything. |
||||
if test x"[$]{with_uc_$1}" = x ; then |
||||
with_uc_$1="$3" |
||||
fi |
||||
|
||||
AC_MSG_CHECKING([Validating $1]) |
||||
tcl_ok=no |
||||
for x in $2 |
||||
do |
||||
if test "[$]x" = "[$]with_uc_$1" ; then |
||||
tcl_ok=yes |
||||
break |
||||
fi |
||||
done |
||||
if test "[$]tcl_ok" = "no" ; then |
||||
AC_MSG_ERROR([Illegal value [$]with_uc_$1, expected one of: $2]) |
||||
else |
||||
AC_MSG_RESULT([[$]with_uc_$1]) |
||||
fi |
||||
|
||||
CRITCL_UCONFIG_$1="-with-$1 \"[$]with_uc_$1\"" |
||||
AC_SUBST([CRITCL_UCONFIG_$1]) |
||||
]) |
||||
|
||||
#------------------------------------------------------------------------ |
||||
# CRITCL_TEA_BOOL_CONFIG -- |
||||
# |
||||
# Declare a --disable/enable-FOO option, with default. |
||||
# |
||||
# Arguments: |
||||
# Name of the option. |
||||
# Default value. |
||||
# Option description. |
||||
# |
||||
# Requires: |
||||
# Results: |
||||
# Adds a --enable-[$1] switch to configure. |
||||
# |
||||
# Substs the following vars: |
||||
# CRITCL_UCONFIG_$1 |
||||
#------------------------------------------------------------------------ |
||||
|
||||
AC_DEFUN([CRITCL_TEA_BOOL_CONFIG],[ |
||||
# CRITCL_TEA_BOOL_CONFIG: $1 |
||||
AC_ARG_ENABLE([$1], |
||||
AC_HELP_STRING([--enable-$1],[$3]), |
||||
[bool_uc_$1=${enableval}] |
||||
[bool_uc_$1="$2"]) |
||||
|
||||
if test "bool_uc_$1" = "yes" ; then |
||||
CRITCL_UCONFIG_$1="-enable $1" |
||||
else |
||||
CRITCL_UCONFIG_$1="-disable $1" |
||||
fi |
||||
|
||||
AC_SUBST([CRITCL_UCONFIG_$1]) |
||||
]) |
@ -0,0 +1,151 @@
|
||||
# Configure for |
||||
# @@PNAME@@ @@PMAJORV@@.@@PMINORV@@ |
||||
# |
||||
# Copyright (c) @@YEAR@@ @@PORG@@ |
||||
# |
||||
# Generated by @@CRITCL@@ |
||||
# At @@NOW@@ |
||||
|
||||
AC_INIT([@@PNAME@@],[@@PMAJORV@@.@@PMINORV@@]) |
||||
|
||||
TEA_INIT([3.9]) |
||||
|
||||
AC_CONFIG_AUX_DIR(tclconfig) |
||||
|
||||
#-------------------------------------------------------------------- |
||||
# Configure script for package '@@PNAME@@'. |
||||
# TEA compliant. |
||||
#-------------------------------------------------------------------- |
||||
|
||||
#-------------------------------------------------------------------- |
||||
# Load the tclConfig.sh file |
||||
#-------------------------------------------------------------------- |
||||
|
||||
TEA_PATH_TCLCONFIG |
||||
TEA_LOAD_TCLCONFIG |
||||
|
||||
#----------------------------------------------------------------------- |
||||
## Std TEA setup |
||||
|
||||
TEA_PREFIX |
||||
TEA_SETUP_COMPILER |
||||
TEA_PUBLIC_TCL_HEADERS |
||||
#TEA_PRIVATE_TCL_HEADERS |
||||
TEA_ENABLE_THREADS |
||||
TEA_ENABLE_SHARED |
||||
TEA_CONFIG_CFLAGS |
||||
TEA_ENABLE_SYMBOLS |
||||
AC_DEFINE(USE_TCL_STUBS) |
||||
TEA_MAKE_LIB |
||||
TEA_PROG_TCLSH |
||||
@@API@@ |
||||
@@UCONFIG@@ |
||||
#----------------------------------------------------------------------- |
||||
## Convert the TEA settings determined by the macros in the last |
||||
## section into something critcl can use throughts configuration. |
||||
|
||||
AC_MSG_RESULT([critcl config: derived from core TEA]) |
||||
|
||||
#AC_MSG_RESULT([critcl config: CC............. ${CC}]) |
||||
#AC_MSG_RESULT([critcl config: CFLAGS......... ${CFLAGS}]) |
||||
#AC_MSG_RESULT([critcl config: SHLIB_LD....... ${SHLIB_LD}]) |
||||
#AC_MSG_RESULT([critcl config: LIBS........... ${LIBS}| |
||||
#AC_MSG_RESULT([critcl config: MATH_LIBS...... ${MATH_LIBS}]) |
||||
#AC_MSG_RESULT([critcl config: CFLAGS_DEFAULT. ${CFLAGS_DEFAULT}]) |
||||
#AC_MSG_RESULT([critcl config: CFLAGS_WARNING. ${CFLAGS_WARNING}]) |
||||
#AC_MSG_RESULT([critcl config: SHLIB_CFLAGS... ${SHLIB_CFLAGS}]) |
||||
#AC_MSG_RESULT([critcl config: LDFLAGS_DEFAULT ${LDFLAGS_DEFAULT}]) |
||||
|
||||
#----------------------------------------------------------------------- |
||||
## 1. Basic/foundational translation. |
||||
|
||||
CRITCL_CC="$(eval echo ${CC} -c ${CFLAGS})" |
||||
CRITCL_VERSION="${CC} -v" |
||||
CRITCL_LINK="$(eval echo $(eval echo ${SHLIB_LD} ${LIBS} ${MATH_LIBS}))" |
||||
CRITCL_PLATFORM="$(${TCLSH_PROG} ${srcdir}/critcl/main.tcl -showtarget)" |
||||
CRITCL_LD_DBG="" |
||||
CRITCL_LD_REL="" |
||||
CRITCL_CC_OUTPUT="-o [[list \$outfile]]" |
||||
CRITCL_LD_OUTPUT="" |
||||
|
||||
if test "${GCC}" = "yes" |
||||
then |
||||
CRITCL_CPP_DEFINE="${CPP} -dM" |
||||
else |
||||
CRITCL_CPP_DEFINE="${CPP}" |
||||
|
||||
if test "${TEA_PLATFORM}" = "windows" |
||||
then |
||||
# windows, no gcc => msvc |
||||
CRITCL_CC_OUTPUT="[[list -Fo\$outfile]]" |
||||
CRITCL_LD_OUTPUT="-dll [[list -out:\$outfile]]" |
||||
CRITCL_LD_DBG="-debug:full -debugtype:cv -verbose:lib" |
||||
CRITCL_LD_REL="-release -opt:ref -opt:icf,3 -ws:aggressive -verbose:lib" |
||||
if test "$do64bit" = "no" ; then |
||||
# 32bit |
||||
CRITCL_LD_DBG="$CRITCL_LD_DBG -nodefaultlib:libc" |
||||
fi |
||||
fi |
||||
fi |
||||
CRITCL_CPP_ENUM="${CPP}" |
||||
|
||||
#----------------------------------------------------------------------- |
||||
## 2. Fine tuning the commands, this now is platform specific. |
||||
|
||||
case $(uname -s) in |
||||
Darwin*) |
||||
AC_MSG_RESULT([critcl config: darwin specific tune-up]) |
||||
|
||||
# - Critcl, due to essentially generating its private |
||||
# lib{tcl,tk}stub.a does generate common symbols for the |
||||
# stubs tables. Use of -fno-common then prevents linking the |
||||
# object files. |
||||
# |
||||
# - A version 0.0 as pulled from TEA package version is not |
||||
# liked by the Darwin gcc either (Example: crimp). |
||||
|
||||
CRITCL_CC="$(echo "$CRITCL_CC" |sed -e 's,-fno-common,,g')" |
||||
CRITCL_LINK="$(echo "$CRITCL_LINK"|sed -e 's,-fno-common,,g')" |
||||
CRITCL_LINK="$(echo "$CRITCL_LINK"|sed -e 's,-current_version 0\.0,,g')" |
||||
CRITCL_LINK="$(echo "$CRITCL_LINK"|sed -e 's,-compatibility_version 0\.0,,g')" |
||||
;; |
||||
*) |
||||
;; |
||||
esac |
||||
|
||||
#----------------------------------------------------------------------- |
||||
## Conversion results |
||||
|
||||
AC_MSG_RESULT([critcl config: platform.......... $CRITCL_PLATFORM]) |
||||
AC_MSG_RESULT([critcl config: compile........... $CRITCL_CC]) |
||||
AC_MSG_RESULT([critcl config: link.............. $CRITCL_LINK]) |
||||
AC_MSG_RESULT([critcl config: cpp define........ $CRITCL_CPP_DEFINE]) |
||||
AC_MSG_RESULT([critcl config: cpp enum.......... $CRITCL_CPP_ENUM]) |
||||
AC_MSG_RESULT([critcl config: version inquiry... $CRITCL_VERSION]) |
||||
AC_MSG_RESULT([critcl config: cc output......... $CRITCL_CC_OUTPUT]) |
||||
AC_MSG_RESULT([critcl config: ld output......... $CRITCL_LD_OUTPUT]) |
||||
AC_MSG_RESULT([critcl config: ld debug.......... $CRITCL_LD_DBG]) |
||||
AC_MSG_RESULT([critcl config: ld release........ $CRITCL_LD_REL]) |
||||
|
||||
#----------------------------------------------------------------------- |
||||
|
||||
AC_SUBST(CRITCL_CC) |
||||
AC_SUBST(CRITCL_VERSION) |
||||
AC_SUBST(CRITCL_LINK) |
||||
AC_SUBST(CRITCL_PLATFORM) |
||||
AC_SUBST(CRITCL_CPP_DEFINE) |
||||
AC_SUBST(CRITCL_CPP_ENUM) |
||||
AC_SUBST(CRITCL_CC_OUTPUT) |
||||
AC_SUBST(CRITCL_LD_OUTPUT) |
||||
AC_SUBST(CRITCL_LD_DBG) |
||||
AC_SUBST(CRITCL_LD_REL) |
||||
#AC_SUBST(CRITCL_) |
||||
#AC_SUBST() |
||||
|
||||
#-------------------------------------------------------------------- |
||||
# Finally, substitute all of the various values into the Makefile. |
||||
# You may alternatively have a special pkgIndex.tcl.in or other files |
||||
# which require substituting th AC variables in. Include these here. |
||||
#-------------------------------------------------------------------- |
||||
|
||||
AC_OUTPUT([Makefile Config]) |
@ -0,0 +1,26 @@
|
||||
These files comprise the basic building blocks for a Tcl Extension |
||||
Architecture (TEA) extension. For more information on TEA see: |
||||
|
||||
http://www.tcl.tk/doc/tea/ |
||||
|
||||
This package is part of the Tcl project at SourceForge, and latest |
||||
sources should be available there: |
||||
|
||||
http://tcl.sourceforge.net/ |
||||
|
||||
This package is a freely available open source package. You can do |
||||
virtually anything you like with it, such as modifying it, redistributing |
||||
it, and selling it either in whole or in part. |
||||
|
||||
CONTENTS |
||||
======== |
||||
The following is a short description of the files you will find in |
||||
the sample extension. |
||||
|
||||
README.txt This file |
||||
|
||||
install-sh Program used for copying binaries and script files |
||||
to their install locations. |
||||
|
||||
tcl.m4 Collection of Tcl autoconf macros. Included by a package's |
||||
aclocal.m4 to define TEA_* macros. |
@ -0,0 +1,119 @@
|
||||
#!/bin/sh |
||||
|
||||
# |
||||
# install - install a program, script, or datafile |
||||
# This comes from X11R5; it is not part of GNU. |
||||
# |
||||
# $XConsortium: install.sh,v 1.2 89/12/18 14:47:22 jim Exp $ |
||||
# |
||||
# This script is compatible with the BSD install script, but was written |
||||
# from scratch. |
||||
# |
||||
|
||||
|
||||
# set DOITPROG to echo to test this script |
||||
|
||||
# Don't use :- since 4.3BSD and earlier shells don't like it. |
||||
doit="${DOITPROG-}" |
||||
|
||||
|
||||
# put in absolute paths if you don't have them in your path; or use env. vars. |
||||
|
||||
mvprog="${MVPROG-mv}" |
||||
cpprog="${CPPROG-cp}" |
||||
chmodprog="${CHMODPROG-chmod}" |
||||
chownprog="${CHOWNPROG-chown}" |
||||
chgrpprog="${CHGRPPROG-chgrp}" |
||||
stripprog="${STRIPPROG-strip}" |
||||
rmprog="${RMPROG-rm}" |
||||
|
||||
instcmd="$mvprog" |
||||
chmodcmd="" |
||||
chowncmd="" |
||||
chgrpcmd="" |
||||
stripcmd="" |
||||
rmcmd="$rmprog -f" |
||||
mvcmd="$mvprog" |
||||
src="" |
||||
dst="" |
||||
|
||||
while [ x"$1" != x ]; do |
||||
case $1 in |
||||
-c) instcmd="$cpprog" |
||||
shift |
||||
continue;; |
||||
|
||||
-m) chmodcmd="$chmodprog $2" |
||||
shift |
||||
shift |
||||
continue;; |
||||
|
||||
-o) chowncmd="$chownprog $2" |
||||
shift |
||||
shift |
||||
continue;; |
||||
|
||||
-g) chgrpcmd="$chgrpprog $2" |
||||
shift |
||||
shift |
||||
continue;; |
||||
|
||||
-s) stripcmd="$stripprog" |
||||
shift |
||||
continue;; |
||||
|
||||
*) if [ x"$src" = x ] |
||||
then |
||||
src=$1 |
||||
else |
||||
dst=$1 |
||||
fi |
||||
shift |
||||
continue;; |
||||
esac |
||||
done |
||||
|
||||
if [ x"$src" = x ] |
||||
then |
||||
echo "install: no input file specified" |
||||
exit 1 |
||||
fi |
||||
|
||||
if [ x"$dst" = x ] |
||||
then |
||||
echo "install: no destination specified" |
||||
exit 1 |
||||
fi |
||||
|
||||
|
||||
# If destination is a directory, append the input filename; if your system |
||||
# does not like double slashes in filenames, you may need to add some logic |
||||
|
||||
if [ -d $dst ] |
||||
then |
||||
dst="$dst"/`basename $src` |
||||
fi |
||||
|
||||
# Make a temp file name in the proper directory. |
||||
|
||||
dstdir=`dirname $dst` |
||||
dsttmp=$dstdir/#inst.$$# |
||||
|
||||
# Move or copy the file name to the temp name |
||||
|
||||
$doit $instcmd $src $dsttmp |
||||
|
||||
# and set any options; do chmod last to preserve setuid bits |
||||
|
||||
if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; fi |
||||
if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; fi |
||||
if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; fi |
||||
if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; fi |
||||
|
||||
# Now rename the file to the real destination. |
||||
|
||||
$doit $rmcmd $dst |
||||
$doit $mvcmd $dsttmp $dst |
||||
|
||||
|
||||
exit 0 |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,227 @@
|
||||
## -*- tcl -*- |
||||
# # ## ### ##### ######## ############# ##################### |
||||
# Pragmas for MetaData Scanner. |
||||
# n/a |
||||
|
||||
# CriTcl Utility Package for bitmap en- and decoder. |
||||
# Based on i-assoc. |
||||
|
||||
package provide critcl::bitmap 1.1 |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Requirements. |
||||
|
||||
package require Tcl 8.6 ; # Min supported version. |
||||
package require critcl 3.2 |
||||
package require critcl::iassoc |
||||
|
||||
namespace eval ::critcl::bitmap {} |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Implementation -- API: Embed C Code |
||||
|
||||
proc critcl::bitmap::def {name dict {exclusions {}}} { |
||||
# dict: Tcl symbolic name -> (C bit-mask (1)) |
||||
# |
||||
# (Ad 1) Can be numeric, or symbolic, as long as it is a C int |
||||
# expression in the end. |
||||
# |
||||
# (Ad exclusions) |
||||
# Excluded bit-masks cannot be converted back to Tcl |
||||
# symbols. These are usually masks with multiple bits |
||||
# set. Conversion back delivers the individual elements |
||||
# instead of the combined mask. |
||||
# |
||||
# If no exclusions are specified the generated code is |
||||
# simpler, i.e. not containing anything for dealing with |
||||
# exclusions at runtime. |
||||
|
||||
# For the C level opt array we want the elements sorted alphabetically. |
||||
set symbols [lsort -dict [dict keys $dict]] |
||||
set i 0 |
||||
foreach s $symbols { |
||||
set id($s) $i |
||||
incr i |
||||
} |
||||
set last $i |
||||
|
||||
set hasexcl [llength $exclusions] |
||||
set excl {} |
||||
foreach e $exclusions { |
||||
dict set excl $e . |
||||
} |
||||
|
||||
dict for {sym mask} $dict { |
||||
set receivable [expr {![dict exists $excl $mask]}] |
||||
|
||||
set map [list @ID@ $id($sym) @SYM@ $sym @MASK@ $mask @RECV@ $receivable] |
||||
|
||||
if {$hasexcl} { |
||||
append init \n[critcl::at::here!][string map $map { |
||||
data->c [@ID@] = "@SYM@"; |
||||
data->mask [@ID@] = @MASK@; |
||||
data->recv [@ID@] = @RECV@; |
||||
data->tcl [@ID@] = Tcl_NewStringObj ("@SYM@", -1); |
||||
Tcl_IncrRefCount (data->tcl [@ID@]); |
||||
}] |
||||
} else { |
||||
append init \n[critcl::at::here!][string map $map { |
||||
data->c [@ID@] = "@SYM@"; |
||||
data->mask [@ID@] = @MASK@; |
||||
data->tcl [@ID@] = Tcl_NewStringObj ("@SYM@", -1); |
||||
Tcl_IncrRefCount (data->tcl [@ID@]); |
||||
}] |
||||
} |
||||
|
||||
append final \n[critcl::at::here!][string map $map { |
||||
Tcl_DecrRefCount (data->tcl [@ID@]); |
||||
}] |
||||
} |
||||
append init \n " data->c \[$last\] = NULL;" |
||||
|
||||
lappend map @NAME@ $name |
||||
lappend map @UNAME@ [string toupper $name] |
||||
lappend map @LAST@ $last |
||||
|
||||
# I. Generate a header file for inclusion by other parts of the |
||||
# package, i.e. csources. Include the header here as well, for |
||||
# the following blocks of code. |
||||
# |
||||
# Declaration of the en- and decoder functions. |
||||
|
||||
critcl::include [critcl::make ${name}.h \n[critcl::at::here!][string map $map { |
||||
#ifndef @NAME@_HEADER |
||||
#define @NAME@_HEADER |
||||
|
||||
/* Encode a flag list into the corresponding bitset */ |
||||
extern int |
||||
@NAME@_encode (Tcl_Interp* interp, |
||||
Tcl_Obj* flags, |
||||
int* result); |
||||
|
||||
/* Decode a bitset into the corresponding flag list */ |
||||
extern Tcl_Obj* |
||||
@NAME@_decode (Tcl_Interp* interp, |
||||
int mask); |
||||
|
||||
#endif |
||||
}]] |
||||
|
||||
# II: Generate the interp association holding the various |
||||
# conversion maps. |
||||
|
||||
if {$hasexcl} { |
||||
critcl::iassoc def ${name}_iassoc {} \n[critcl::at::here!][string map $map { |
||||
const char* c [@LAST@+1]; /* Bit name, C string */ |
||||
Tcl_Obj* tcl [@LAST@]; /* Bit name, Tcl_Obj*, sharable */ |
||||
int mask [@LAST@]; /* Bit mask */ |
||||
int recv [@LAST@]; /* Flag, true for receivable event */ |
||||
}] $init $final |
||||
} else { |
||||
critcl::iassoc def ${name}_iassoc {} \n[critcl::at::here!][string map $map { |
||||
const char* c [@LAST@+1]; /* Bit name, C string */ |
||||
Tcl_Obj* tcl [@LAST@]; /* Bit name, Tcl_Obj*, sharable */ |
||||
int mask [@LAST@]; /* Bit mask */ |
||||
}] $init $final |
||||
} |
||||
|
||||
# III: Generate encoder function: Conversion of list of flag names |
||||
# into corresponding bitset. |
||||
|
||||
critcl::ccode \n[critcl::at::here!][string map $map { |
||||
int |
||||
@NAME@_encode (Tcl_Interp* interp, |
||||
Tcl_Obj* flags, |
||||
int* result) |
||||
{ |
||||
@NAME@_iassoc_data context = @NAME@_iassoc (interp); |
||||
int mask, lc, i, id; |
||||
Tcl_Obj** lv; |
||||
|
||||
if (Tcl_ListObjGetElements (interp, flags, &lc, &lv) != TCL_OK) { |
||||
return TCL_ERROR; |
||||
} |
||||
|
||||
mask = 0; |
||||
for (i = 0; i < lc; i++) { |
||||
if (Tcl_GetIndexFromObj (interp, lv[i], context->c, "@NAME@", 0, |
||||
&id) != TCL_OK) { |
||||
Tcl_SetErrorCode (interp, "@UNAME@", "FLAG", NULL); |
||||
return TCL_ERROR; |
||||
} |
||||
mask |= context->mask [id]; |
||||
} |
||||
|
||||
*result = mask; |
||||
return TCL_OK; |
||||
} |
||||
}] |
||||
|
||||
# IV: Generate decoder function: Convert bitset into the |
||||
# corresponding list of flag names. |
||||
|
||||
if {$hasexcl} { |
||||
critcl::ccode \n[critcl::at::here!][string map $map { |
||||
Tcl_Obj* |
||||
@NAME@_decode (Tcl_Interp* interp, int mask) |
||||
{ |
||||
int i; |
||||
@NAME@_iassoc_data context = @NAME@_iassoc (interp); |
||||
Tcl_Obj* res = Tcl_NewListObj (0, NULL); |
||||
|
||||
for (i = 0; i < @LAST@; i++) { |
||||
if (!context->recv[i]) continue; |
||||
if (!(mask & context->mask[i])) continue; |
||||
(void) Tcl_ListObjAppendElement (interp, res, context->tcl [i]); |
||||
} |
||||
return res; |
||||
} |
||||
}] |
||||
} else { |
||||
critcl::ccode \n[critcl::at::here!][string map $map { |
||||
Tcl_Obj* |
||||
@NAME@_decode (Tcl_Interp* interp, int mask) |
||||
{ |
||||
int i; |
||||
@NAME@_iassoc_data context = @NAME@_iassoc (interp); |
||||
Tcl_Obj* res = Tcl_NewListObj (0, NULL); |
||||
|
||||
for (i = 0; i < @LAST@; i++) { |
||||
if (!(mask & context->mask[i])) continue; |
||||
(void) Tcl_ListObjAppendElement (interp, res, context->tcl [i]); |
||||
} |
||||
return res; |
||||
} |
||||
}] |
||||
} |
||||
|
||||
# V. Define convenient argument- and result-type definitions |
||||
# wrapping the de- and encoder functions for use by cprocs. |
||||
|
||||
critcl::argtype $name \n[critcl::at::here!][string map $map { |
||||
if (@NAME@_encode (interp, @@, &@A) != TCL_OK) return TCL_ERROR; |
||||
}] int int |
||||
|
||||
critcl::resulttype $name \n[critcl::at::here!][string map $map { |
||||
/* @NAME@_decode result is 0-refcount */ |
||||
Tcl_SetObjResult (interp, @NAME@_decode (interp, rv)); |
||||
return TCL_OK; |
||||
}] int |
||||
} |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Export API |
||||
|
||||
namespace eval ::critcl::bitmap { |
||||
namespace export def |
||||
catch { namespace ensemble create } |
||||
} |
||||
|
||||
namespace eval ::critcl { |
||||
namespace export bitmap |
||||
catch { namespace ensemble create } |
||||
} |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Ready |
||||
return |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue