1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/ext/tk/lib/multi-tk.rb
nagai 41d41fbdb9 * ext/tk/stubs.c: When --enable-tcltk-stubs, the initialize
routine creates a Tcl/Tk interpreter and deletes it. However,
  init cost of Tk's MainWindow is not so small. And that makes it
  impossible to use libraries written with Tcl functions only on
  an environment without a graphical display. This changes support
  delaying initalization of Tk_Stubs until the script needs Tk.
* ext/tk/stubs.h: New file. Define prototypes and return codes of
  functions on stubs.c.
* ext/tk/tcltklib.c: Support delaying initalization of Tk_Stubs
  until the script needs Tk.
* ext/tk/tcltklib.c: Show friendly error messages for errors on
  initialization.
* ext/tk/tcltklib.c: Avoid SEGV on ip_finalize() when ruby is
  exiting and $DEBUG is true. (Not fix. If you know the reason of
  why, please fix it.)
* ext/tk/tkutil/tkutil.c (ary2list, ary2list2): bug fix on handling
  of encoding.
* ext/tk/lib/multi-tk.rb: MultiTkIp#eval_string and bg_eval_string
  don't work propery.
* ext/tk/lib/tk.rb: Forget extending Tk::Encoding module to Tk.
* ext/tk/lib/tk/variable.rb: TkVarAccess fails to initialize the
  object for an element of a Tcl's array variable.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@8860 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2005-07-28 09:14:59 +00:00

2657 lines
65 KiB
Ruby

#
# multi-tk.rb - supports multi Tk interpreters
# by Hidetoshi NAGAI <nagai@ai.kyutech.ac.jp>
require 'tcltklib'
require 'tkutil'
require 'thread'
if defined? Tk
fail RuntimeError,"'multi-tk' library must be required before requiring 'tk'"
end
################################################
# ignore exception on the mainloop?
TclTkLib.mainloop_abort_on_exception = true
# TclTkLib.mainloop_abort_on_exception = false
# TclTkLib.mainloop_abort_on_exception = nil
################################################
# add ThreadGroup check to TclTkIp.new
class << TclTkIp
alias __new__ new
private :__new__
def new(*args)
if Thread.current.group != ThreadGroup::Default
raise SecurityError, 'only ThreadGroup::Default can call TclTkIp.new'
end
__new__(*args)
end
end
################################################
# exceptiopn to treat the return value from IP
class MultiTkIp_OK < Exception
def self.send(thread, ret=nil)
thread.raise self.new(ret)
end
def initialize(ret=nil)
super('succeed')
@return_value = ret
end
attr_reader :return_value
alias value return_value
end
MultiTkIp_OK.freeze
################################################
# methods for construction
class MultiTkIp
@@SLAVE_IP_ID = ['slave'.freeze, '0'.taint].freeze
@@IP_TABLE = {}.taint unless defined?(@@IP_TABLE)
@@INIT_IP_ENV = [].taint unless defined?(@@INIT_IP_ENV) # table of Procs
@@ADD_TK_PROCS = [].taint unless defined?(@@ADD_TK_PROCS) # table of [name, args, body]
@@TK_TABLE_LIST = [].taint unless defined?(@@TK_TABLE_LIST)
@@TK_CMD_TBL = {}.taint unless defined?(@@TK_CMD_TBL)
######################################
@@CB_ENTRY_CLASS = Class.new(TkCallbackEntry){|c|
def initialize(ip, cmd)
@ip = ip
@cmd = cmd
freeze
end
attr_reader :ip, :cmd
def inspect
cmd.inspect
end
def call(*args)
unless @ip.deleted?
current = Thread.current
backup_ip = current['callback_ip']
current['callback_ip'] = @ip
begin
@ip.cb_eval(@cmd, *args)
rescue TkCallbackBreak, TkCallbackContinue => e
fail e
rescue SecurityError => e
# in 'exit', 'exit!', and 'abort' : security error --> delete IP
if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/
@ip.delete
elsif @ip.safe?
if @ip.respond_to?(:cb_error)
@ip.cb_error(e)
else
nil # ignore
end
else
fail e
end
rescue Exception => e
if @ip.safe?
if @ip.respond_to?(:cb_error)
@ip.cb_error(e)
else
nil # ignore
end
else
fail e
end
ensure
current['callback_ip'] = backup_ip
end
end
end
}.freeze
######################################
def _keys2opts(src_keys)
return nil if src_keys == nil
keys = {}; src_keys.each{|k, v| keys[k.to_s] = v}
#keys.collect{|k,v| "-#{k} #{v}"}.join(' ')
keys.collect{|k,v| "-#{k} #{TclTkLib._conv_listelement(TkComm::_get_eval_string(v))}"}.join(' ')
end
private :_keys2opts
def _check_and_return(thread, exception, wait=0)
unless thread
unless exception.kind_of?(MultiTkIp_OK)
msg = "#{exception.class}: #{exception.message}"
if @interp.deleted?
warn("Warning (#{self}): " + msg)
return nil
end
if safe?
warn("Warning (#{self}): " + msg) if $DEBUG
return nil
end
begin
@interp._eval_without_enc(@interp._merge_tklist('bgerror', msg))
rescue Exception => e
warn("Warning (#{self}): " + msg)
end
end
return nil
end
if wait == 0
# no wait
Thread.pass
if thread.stop?
thread.raise exception
end
return thread
end
# wait to stop the caller thread
wait.times{
if thread.stop?
# ready to send exception
thread.raise exception
return thread
end
# wait
Thread.pass
}
# unexpected error
thread.raise RuntimeError, "the thread may not wait for the return value"
return thread
end
######################################
def set_cb_error(cmd = Proc.new)
@cb_error_proc[0] = cmd
end
def cb_error(e)
if @cb_error_proc[0].respond_to?(:call)
@cb_error_proc[0].call(e)
end
end
######################################
def set_safe_level(safe)
if safe > @safe_level[0]
@safe_level[0] = safe
@cmd_queue.enq([@system, 'set_safe_level', safe])
end
@safe_level[0]
end
def safe_level=(safe)
set_safe_level(safe)
end
def self.set_safe_level(safe)
__getip.set_safe_level(safe)
end
def self.safe_level=(safe)
self.set_safe_level(safe)
end
def safe_level
@safe_level[0]
end
def self.safe_level
__getip.safe_level
end
def wait_on_mainloop?
@wait_on_mainloop[0]
end
def wait_on_mainloop=(bool)
@wait_on_mainloop[0] = bool
end
def running_mainloop?
@wait_on_mainloop[1] > 0
end
def _destroy_slaves_of_slaveIP(ip)
unless ip.deleted?
# ip._split_tklist(ip._invoke('interp', 'slaves')).each{|name|
ip._split_tklist(ip._invoke_without_enc('interp', 'slaves')).each{|name|
name = _fromUTF8(name)
begin
# ip._eval_without_enc("#{name} eval {foreach i [after info] {after cancel $i}}")
after_ids = ip._eval_without_enc("#{name} eval {after info}")
ip._eval_without_enc("#{name} eval {foreach i {#{after_ids}} {after cancel $i}}")
rescue Exception
end
begin
# ip._invoke('interp', 'eval', name, 'destroy', '.')
ip._invoke(name, 'eval', 'destroy', '.')
rescue Exception
end
# safe_base?
if ip._eval_without_enc("catch {::safe::interpConfigure #{name}}") == '0'
begin
ip._eval_without_enc("::safe::interpDelete #{name}")
rescue Exception
end
end
=begin
if ip._invoke('interp', 'exists', name) == '1'
begin
ip._invoke(name, 'eval', 'exit')
rescue Exception
end
end
=end
unless ip.deleted?
if ip._invoke('interp', 'exists', name) == '1'
begin
ip._invoke('interp', 'delete', name)
rescue Exception
end
end
end
}
end
end
def _receiver_eval_proc_core(safe_level, thread, cmd, *args)
begin
#ret = proc{$SAFE = safe_level; cmd.call(*args)}.call
#ret = cmd.call(safe_level, *args)
normal_ret = false
ret = catch(:IRB_EXIT) do # IRB hack
retval = cmd.call(safe_level, *args)
normal_ret = true
retval
end
unless normal_ret
# catch IRB_EXIT
exit(ret)
end
ret
rescue SystemExit => e
# delete IP
unless @interp.deleted?
@slave_ip_tbl.each{|name, subip|
_destroy_slaves_of_slaveIP(subip)
begin
# subip._eval_without_enc("foreach i [after info] {after cancel $i}")
after_ids = subip._eval_without_enc("after info")
subip._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
rescue Exception
end
=begin
begin
subip._invoke('destroy', '.') unless subip.deleted?
rescue Exception
end
=end
# safe_base?
if @interp._eval_without_enc("catch {::safe::interpConfigure #{name}}") == '0'
begin
@interp._eval_without_enc("::safe::interpDelete #{name}")
rescue Exception
else
next if subip.deleted?
end
end
if subip.respond_to?(:safe_base?) && subip.safe_base? &&
!subip.deleted?
# do 'exit' to call the delete_hook procedure
begin
subip._eval_without_enc('exit')
rescue Exception
end
else
begin
subip.delete unless subip.deleted?
rescue Exception
end
end
}
begin
# @interp._eval_without_enc("foreach i [after info] {after cancel $i}")
after_ids = @interp._eval_without_enc("after info")
@interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
rescue Exception
end
begin
@interp._invoke('destroy', '.') unless @interp.deleted?
rescue Exception
end
if @safe_base && !@interp.deleted?
# do 'exit' to call the delete_hook procedure
@interp._eval_without_enc('exit')
else
@interp.delete unless @interp.deleted?
end
end
if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/
_check_and_return(thread, MultiTkIp_OK.new($3 == 'exit'))
else
_check_and_return(thread, MultiTkIp_OK.new(nil))
end
# if master? && !safe? && allow_ruby_exit?
if !@interp.deleted? && master? && !safe? && allow_ruby_exit?
=begin
ObjectSpace.each_object(TclTkIp){|obj|
obj.delete unless obj.deleted?
}
=end
#exit(e.status)
fail e
end
# break
rescue SecurityError => e
# in 'exit', 'exit!', and 'abort' : security error --> delete IP
if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/
ret = ($3 == 'exit')
unless @interp.deleted?
@slave_ip_tbl.each{|name, subip|
_destroy_slaves_of_slaveIP(subip)
begin
# subip._eval_without_enc("foreach i [after info] {after cancel $i}")
after_ids = subip._eval_without_enc("after info")
subip._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
rescue Exception
end
=begin
begin
subip._invoke('destroy', '.') unless subip.deleted?
rescue Exception
end
=end
# safe_base?
if @interp._eval_without_enc("catch {::safe::interpConfigure #{name}}") == '0'
begin
@interp._eval_without_enc("::safe::interpDelete #{name}")
rescue Exception
else
next if subip.deleted?
end
end
if subip.respond_to?(:safe_base?) && subip.safe_base? &&
!subip.deleted?
# do 'exit' to call the delete_hook procedure
begin
subip._eval_without_enc('exit')
rescue Exception
end
else
begin
subip.delete unless subip.deleted?
rescue Exception
end
end
}
begin
# @interp._eval_without_enc("foreach i [after info] {after cancel $i}")
after_ids = @interp._eval_without_enc("after info")
@interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
rescue Exception
end
=begin
begin
@interp._invoke('destroy', '.') unless @interp.deleted?
rescue Exception
end
=end
if @safe_base && !@interp.deleted?
# do 'exit' to call the delete_hook procedure
@interp._eval_without_enc('exit')
else
@interp.delete unless @interp.deleted?
end
end
_check_and_return(thread, MultiTkIp_OK.new(ret))
# break
else
# raise security error
_check_and_return(thread, e)
end
rescue Exception => e
# raise exception
begin
bt = _toUTF8(e.backtrace.join("\n"))
bt.instance_variable_set(:@encoding, 'utf-8')
rescue Exception
bt = e.backtrace.join("\n")
end
begin
@interp._set_global_var('errorInfo', bt)
rescue Exception
end
_check_and_return(thread, e)
else
# no exception
_check_and_return(thread, MultiTkIp_OK.new(ret))
end
end
def _receiver_eval_proc(last_thread, safe_level, thread, cmd, *args)
if thread
Thread.new{
last_thread.join if last_thread
unless @interp.deleted?
_receiver_eval_proc_core(safe_level, thread, cmd, *args)
end
}
else
Thread.new{
unless @interp.deleted?
_receiver_eval_proc_core(safe_level, thread, cmd, *args)
end
}
last_thread
end
end
private :_receiver_eval_proc, :_receiver_eval_proc_core
def _receiver_mainloop(check_root)
if @evloop_thread[0] && @evloop_thread[0].alive?
@evloop_thread[0]
else
@evloop_thread[0] = Thread.new{
while !@interp.deleted?
#if check_root
# inf = @interp._invoke_without_enc('info', 'command', '.')
# break if !inf.kind_of?(String) || inf != '.'
#end
break if check_root && !@interp.has_mainwindow?
sleep 0.5
end
}
@evloop_thread[0]
end
end
def _create_receiver_and_watchdog(lvl = $SAFE)
lvl = $SAFE if lvl < $SAFE
# command-procedures receiver
receiver = Thread.new(lvl){|safe_level|
last_thread = {}
loop do
break if @interp.deleted?
thread, cmd, *args = @cmd_queue.deq
if thread == @system
# control command
case cmd
when 'set_safe_level'
begin
safe_level = args[0] if safe_level < args[0]
rescue Exception
end
when 'call_mainloop'
thread = args.shift
_check_and_return(thread,
MultiTkIp_OK.new(_receiver_mainloop(*args)))
else
# ignore
end
else
# procedure
last_thread[thread] = _receiver_eval_proc(last_thread[thread],
safe_level, thread,
cmd, *args)
end
end
}
# watchdog of receiver
watchdog = Thread.new{
begin
loop do
sleep 1
break unless receiver.alive?
end
rescue Exception
# ignore all kind of Exception
end
# receiver is dead
loop do
thread, cmd, *args = @cmd_queue.deq
next unless thread
if thread.alive?
if @interp.deleted?
thread.raise RuntimeError, 'the interpreter is already deleted'
else
thread.raise RuntimeError,
'the interpreter no longer receives command procedures'
end
end
end
}
# return threads
[receiver, watchdog]
end
private :_check_and_return, :_create_receiver_and_watchdog
######################################
if self.const_defined? :DEFAULT_MASTER_NAME
name = DEFAULT_MASTER_NAME.to_s
else
name = nil
end
if self.const_defined?(:DEFAULT_MASTER_OPTS) &&
DEFAULT_MASTER_OPTS.kind_of?(Hash)
keys = DEFAULT_MASTER_OPTS
else
keys = {}
end
@@DEFAULT_MASTER = self.allocate
@@DEFAULT_MASTER.instance_eval{
@tk_windows = {}.taint
@tk_table_list = [].taint
@slave_ip_tbl = {}.taint
@slave_ip_top = {}.taint
@evloop_thread = [].taint
unless keys.kind_of? Hash
fail ArgumentError, "expecting a Hash object for the 2nd argument"
end
@interp = TclTkIp.new(name, _keys2opts(keys))
@ip_name = nil
@system = Object.new
@wait_on_mainloop = [true, 0].taint
@threadgroup = Thread.current.group
@safe_base = false
@safe_level = [$SAFE]
@cmd_queue = Queue.new
@cmd_receiver, @receiver_watchdog = _create_receiver_and_watchdog(@safe_level[0])
@threadgroup.add @cmd_receiver
@threadgroup.add @receiver_watchdog
# NOT enclose @threadgroup for @@DEFAULT_MASTER
@@IP_TABLE[ThreadGroup::Default] = self
@@IP_TABLE[@threadgroup] = self
#################################
@assign_request = Class.new(Exception){
def self.new(target, ret)
obj = super()
obj.target = target
obj.ret = ret
obj
end
attr_accessor :target, :ret
}
@assign_thread = Thread.new{
loop do
begin
Thread.stop
rescue @assign_request=>req
begin
req.ret[0] = req.target.instance_eval{
@cmd_receiver, @receiver_watchdog =
_create_receiver_and_watchdog(@safe_level[0])
@threadgroup.add @cmd_receiver
@threadgroup.add @receiver_watchdog
@threadgroup.enclose
true
}
rescue Exception=>e
begin
req.ret[0] = e
rescue Exception
# ignore
end
end
rescue Exception
# ignore
end
end
}
def self.assign_receiver_and_watchdog(target)
ret = [nil]
@assign_thread.raise(@assign_request.new(target, ret))
while ret[0] == nil
unless @assign_thread.alive?
raise RuntimeError, 'lost the thread to assign a receiver and a watchdog thread'
end
end
if ret[0].kind_of?(Exception)
raise ret[0]
else
ret[0]
end
end
#################################
class << self
undef :instance_eval
end
}
@@DEFAULT_MASTER.freeze # defend against modification
######################################
def self.inherited(subclass)
# trust if on ThreadGroup::Default or @@DEFAULT_MASTER's ThreadGroup
if @@IP_TABLE[Thread.current.group] == @@DEFAULT_MASTER
begin
class << subclass
self.methods.each{|m|
begin
unless m == '__id__' || m == '__send__' || m == 'freeze'
undef_method(m)
end
rescue Exception
# ignore all exceptions
end
}
end
ensure
subclass.freeze
fail SecurityError,
"cannot create subclass of MultiTkIp on a untrusted ThreadGroup"
end
end
end
######################################
@@SAFE_OPT_LIST = [
'accessPath'.freeze,
'statics'.freeze,
'nested'.freeze,
'deleteHook'.freeze
].freeze
def _parse_slaveopts(keys)
name = nil
safe = false
safe_opts = {}
tk_opts = {}
keys.each{|k,v|
k_str = k.to_s
if k_str == 'name'
name = v
elsif k_str == 'safe'
safe = v
elsif @@SAFE_OPT_LIST.member?(k_str)
safe_opts[k_str] = v
else
tk_opts[k_str] = v
end
}
if keys['without_tk'] || keys[:without_tk]
[name, safe, safe_opts, nil]
else
[name, safe, safe_opts, tk_opts]
end
end
private :_parse_slaveopts
def _create_slave_ip_name
name = @@SLAVE_IP_ID.join('')
@@SLAVE_IP_ID[1].succ!
name
end
private :_create_slave_ip_name
######################################
def __check_safetk_optkeys(optkeys)
# based on 'safetk.tcl'
new_keys = {}
optkeys.each{|k,v| new_keys[k.to_s] = v}
# check 'display'
if !new_keys.key?('display')
begin
#new_keys['display'] = @interp._invoke('winfo screen .')
new_keys['display'] = @interp._invoke('winfo', 'screen', '.')
rescue
if ENV[DISPLAY]
new_keys['display'] = ENV[DISPLAY]
elsif !new_keys.key?('use')
warn "Warning: no screen info or ENV[DISPLAY], so use ':0.0'"
new_keys['display'] = ':0.0'
end
end
end
# check 'use'
if new_keys.key?('use')
# given 'use'
case new_keys['use']
when TkWindow
new_keys['use'] = TkWinfo.id(new_keys['use'])
#assoc_display = @interp._eval('winfo screen .')
assoc_display = @interp._invoke('winfo', 'screen', '.')
when /^\..*/
new_keys['use'] = @interp._invoke('winfo', 'id', new_keys['use'])
assoc_display = @interp._invoke('winfo', 'screen', new_keys['use'])
else
begin
pathname = @interp._invoke('winfo', 'pathname', new_keys['use'])
assoc_display = @interp._invoke('winfo', 'screen', pathname)
rescue
assoc_display = new_keys['display']
end
end
# match display?
if assoc_display != new_keys['display']
if optkeys.key?(:display) || optkeys.key?('display')
fail RuntimeError,
"conflicting 'display'=>#{new_keys['display']} " +
"and display '#{assoc_display}' on 'use'=>#{new_keys['use']}"
else
new_keys['display'] = assoc_display
end
end
end
# return
new_keys
end
private :__check_safetk_optkeys
def __create_safetk_frame(slave_ip, slave_name, app_name, keys)
# display option is used by ::safe::loadTk
loadTk_keys = {}
loadTk_keys['display'] = keys['display']
dup_keys = keys.dup
# keys for toplevel : allow followings
toplevel_keys = {}
['height', 'width', 'background', 'menu'].each{|k|
toplevel_keys[k] = dup_keys.delete(k) if dup_keys.key?(k)
}
toplevel_keys['classname'] = 'SafeTk'
toplevel_keys['screen'] = dup_keys.delete('display')
# other keys used by pack option of container frame
# create toplevel widget
begin
top = TkToplevel.new(toplevel_keys)
rescue NameError => e
fail e unless @interp.safe?
fail SecurityError, "unable create toplevel on the safe interpreter"
end
msg = "Untrusted Ruby/Tk applet (#{slave_name})"
if app_name.kind_of?(String)
top.title "#{app_name} (#{slave_name})"
else
top.title msg
end
# procedure to delete slave interpreter
slave_delete_proc = proc{
unless slave_ip.deleted?
#if slave_ip._invoke('info', 'command', '.') != ""
# slave_ip._invoke('destroy', '.')
#end
#slave_ip.delete
slave_ip._eval_without_enc('exit')
end
begin
top.destroy if top.winfo_exist?
rescue
# ignore
end
}
tag = TkBindTag.new.bind('Destroy', slave_delete_proc)
top.bindtags = top.bindtags.unshift(tag)
# create control frame
TkFrame.new(top, :bg=>'red', :borderwidth=>3, :relief=>'ridge') {|fc|
fc.bindtags = fc.bindtags.unshift(tag)
TkFrame.new(fc, :bd=>0){|f|
TkButton.new(f,
:text=>'Delete', :bd=>1, :padx=>2, :pady=>0,
:highlightthickness=>0, :command=>slave_delete_proc
).pack(:side=>:right, :fill=>:both)
f.pack(:side=>:right, :fill=>:both, :expand=>true)
}
TkLabel.new(fc, :text=>msg, :padx=>2, :pady=>0,
:anchor=>:w).pack(:side=>:left, :fill=>:both, :expand=>true)
fc.pack(:side=>:bottom, :fill=>:x)
}
# container frame for slave interpreter
dup_keys['fill'] = :both unless dup_keys.key?('fill')
dup_keys['expand'] = true unless dup_keys.key?('expand')
c = TkFrame.new(top, :container=>true).pack(dup_keys)
# return keys
loadTk_keys['use'] = TkWinfo.id(c)
[loadTk_keys, top.path]
end
private :__create_safetk_frame
def __create_safe_slave_obj(safe_opts, app_name, tk_opts)
# safe interpreter
ip_name = _create_slave_ip_name
slave_ip = @interp.create_slave(ip_name, true)
@slave_ip_tbl[ip_name] = slave_ip
def slave_ip.safe_base?
true
end
@interp._eval("::safe::interpInit #{ip_name}")
slave_ip._invoke('set', 'argv0', app_name) if app_name.kind_of?(String)
if tk_opts
tk_opts = __check_safetk_optkeys(tk_opts)
if tk_opts.key?('use')
@slave_ip_top[ip_name] = ''
else
tk_opts, top_path = __create_safetk_frame(slave_ip, ip_name, app_name,
tk_opts)
@slave_ip_top[ip_name] = top_path
end
@interp._eval("::safe::loadTk #{ip_name} #{_keys2opts(tk_opts)}")
else
@slave_ip_top[ip_name] = nil
end
if safe_opts.key?('deleteHook') || safe_opts.key?(:deleteHook)
@interp._eval("::safe::interpConfigure #{ip_name} " +
_keys2opts(safe_opts))
else
@interp._eval("::safe::interpConfigure #{ip_name} " +
_keys2opts(safe_opts) + '-deleteHook {' +
TkComm._get_eval_string(proc{|slave|
self._default_delete_hook(slave)
}) + '}')
end
[slave_ip, ip_name]
end
def __create_trusted_slave_obj(name, keys)
ip_name = _create_slave_ip_name
slave_ip = @interp.create_slave(ip_name, false)
slave_ip._invoke('set', 'argv0', name) if name.kind_of?(String)
slave_ip._invoke('set', 'argv', _keys2opts(keys))
@interp._invoke('load', '', 'Tk', ip_name)
@slave_ip_tbl[ip_name] = slave_ip
[slave_ip, ip_name]
end
######################################
def _create_slave_object(keys={})
ip = MultiTkIp.new_slave(self, keys={})
@slave_ip_tbl[ip.name] = ip
end
######################################
def initialize(master, safeip=true, keys={})
if $SAFE >= 4
fail SecurityError, "cannot create a new interpreter at level #{$SAFE}"
end
if safeip == nil && $SAFE >= 2
fail SecurityError, "cannot create a master-ip at level #{$SAFE}"
end
if master.deleted? && safeip == nil
fail RuntimeError, "cannot create a slave of a deleted interpreter"
end
if !master.deleted? && !master.master? && master.safe?
fail SecurityError, "safe-slave-ip cannot create a new interpreter"
end
if safeip == nil && !master.master?
fail SecurityError, "slave-ip cannot create a master-ip"
end
unless keys.kind_of? Hash
fail ArgumentError, "expecting a Hash object for the 2nd argument"
end
@tk_windows = {}
@tk_table_list = []
@slave_ip_tbl = {}
@slave_ip_top = {}
@cb_error_proc = []
@evloop_thread = []
@tk_windows.taint unless @tk_windows.tainted?
@tk_table_list.taint unless @tk_table_list.tainted?
@slave_ip_tbl.taint unless @slave_ip_tbl.tainted?
@slave_ip_top.taint unless @slave_ip_top.tainted?
@cb_error_proc.taint unless @cb_error_proc.tainted?
@evloop_thread.taint unless @evloop_thread.tainted?
name, safe, safe_opts, tk_opts = _parse_slaveopts(keys)
safe = 4 if safe && !safe.kind_of?(Fixnum)
@safe_base = false
if safeip == nil
# create master-ip
@interp = TclTkIp.new(name, _keys2opts(tk_opts))
@ip_name = nil
if safe
safe = $SAFE if safe < $SAFE
@safe_level = [safe]
else
@safe_level = [$SAFE]
end
else
# create slave-ip
if safeip || master.safe?
@safe_base = true
@interp, @ip_name = master.__create_safe_slave_obj(safe_opts,
name, tk_opts)
if safe
safe = master.safe_level if safe < master.safe_level
@safe_level = [safe]
else
@safe_level = [4]
end
else
@interp, @ip_name = master.__create_trusted_slave_obj(name, tk_opts)
if safe
safe = master.safe_level if safe < master.safe_level
@safe_level = [safe]
else
@safe_level = [master.safe_level]
end
end
@set_alias_proc = proc{|name|
master._invoke('interp', 'alias', @ip_name, name, '', name)
}.freeze
end
@system = Object.new
@wait_on_mainloop = [true, 0].taint
# @wait_on_mainloop = [false, 0].taint
@threadgroup = ThreadGroup.new
@cmd_queue = Queue.new
=begin
@cmd_receiver, @receiver_watchdog = _create_receiver_and_watchdog(@safe_level[0])
@threadgroup.add @cmd_receiver
@threadgroup.add @receiver_watchdog
@threadgroup.enclose
=end
@@DEFAULT_MASTER.assign_receiver_and_watchdog(self)
@@IP_TABLE[@threadgroup] = self
_init_ip_internal(@@INIT_IP_ENV, @@ADD_TK_PROCS)
@@TK_TABLE_LIST.size.times{
(tbl = {}).tainted? || tbl.taint
@tk_table_list << tbl
}
class << self
undef :instance_eval
end
# dummy call for initialization
self.eval_proc{ Tk.tk_call('set', 'tcl_patchLevel') }
self.freeze # defend against modification
end
######################################
def _default_delete_hook(slave)
@slave_ip_tbl.delete(slave)
top = @slave_ip_top.delete(slave)
if top.kind_of?(String)
# call default hook of safetk.tcl (ignore exceptions)
if top == ''
begin
@interp._eval("::safe::disallowTk #{slave}")
rescue
warn("Waring: fail to call '::safe::disallowTk'") if $DEBUG
end
else # toplevel path
begin
@interp._eval("::safe::tkDelete {} #{top} #{slave}")
rescue
warn("Waring: fail to call '::safe::tkDelete'") if $DEBUG
begin
@interp._eval("destroy #{top}")
rescue
warn("Waring: fail to destroy toplevel") if $DEBUG
end
end
end
end
end
end
# get target IP
class MultiTkIp
def self._ip_id_
__getip._ip_id_
end
def _ip_id_
''
end
def self.__getip
current = Thread.current
if TclTkLib.mainloop_thread? != false && current['callback_ip']
return current['callback_ip']
end
if current.group == ThreadGroup::Default
@@DEFAULT_MASTER
else
ip = @@IP_TABLE[current.group]
unless ip
fail SecurityError,
"cannot call Tk methods on #{Thread.current.inspect}"
end
ip
end
end
end
# aliases of constructor
class << MultiTkIp
alias __new new
private :__new
def new_master(safe=nil, keys={})
if safe.kind_of?(Hash)
keys = safe
elsif safe.kind_of?(Integer)
raise ArgumentError, "unexpected argument(s)" unless keys.kind_of?(Hash)
if !keys.key?(:safe) && !keys.key?('safe')
keys[:safe] = safe
end
elsif safe == nil
# do nothing
else
raise ArgumentError, "unexpected argument(s)"
end
ip = __new(__getip, nil, keys)
#ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call) if block_given?
if block_given?
Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call)}
end
ip
end
alias new new_master
def new_slave(safe=nil, keys={})
if safe.kind_of?(Hash)
keys = safe
elsif safe.kind_of?(Integer)
raise ArgumentError, "unexpected argument(s)" unless keys.kind_of?(Hash)
if !keys.key?(:safe) && !keys.key?('safe')
keys[:safe] = safe
end
elsif safe == nil
# do nothing
else
raise ArgumentError, "unexpected argument(s)"
end
ip = __new(__getip, false, keys)
# ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call) if block_given?
if block_given?
Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call)}
end
ip
end
alias new_trusted_slave new_slave
def new_safe_slave(safe=4, keys={})
if safe.kind_of?(Hash)
keys = safe
elsif safe.kind_of?(Integer)
raise ArgumentError, "unexpected argument(s)" unless keys.kind_of?(Hash)
if !keys.key?(:safe) && !keys.key?('safe')
keys[:safe] = safe
end
else
raise ArgumentError, "unexpected argument(s)"
end
ip = __new(__getip, true, keys)
# ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call) if block_given?
if block_given?
Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call)}
end
ip
end
alias new_safeTk new_safe_slave
end
# get info
class MultiTkIp
def inspect
s = self.to_s.chop!
if master?
if @interp.deleted?
s << ':deleted-master'
else
s << ':master'
end
else
if @interp.deleted?
s << ':deleted-slave'
elsif @interp.safe?
s << ':safe-slave'
else
s << ':trusted-slave'
end
end
s << '>'
end
def master?
if @ip_name
false
else
true
end
end
def self.master?
__getip.master?
end
def slave?
not master?
end
def self.slave?
not self.master?
end
def alive?
begin
return false unless @cmd_receiver.alive?
return false if @interp.deleted?
return false if @interp._invoke('interp', 'exists', '') == '0'
rescue Exception
return false
end
true
end
def self.alive?
__getip.alive?
end
def path
@ip_name || ''
end
def self.path
__getip.path
end
def ip_name
@ip_name || ''
end
def self.ip_name
__getip.ip_name
end
def to_eval
@ip_name || ''
end
def self.to_eval
__getip.to_eval
end
def slaves(all = false)
@interp._invoke('interp','slaves').split.map!{|name|
if @slave_ip_tbl.key?(name)
@slave_ip_tbl[name]
elsif all
name
else
nil
end
}.compact!
end
def self.slaves(all = false)
__getip.slaves(all)
end
end
# instance methods to treat tables
class MultiTkIp
def _tk_cmd_tbl
tbl = {}
MultiTkIp.tk_cmd_tbl.each{|id, ent| tbl[id] = ent if ent.ip == self }
tbl
end
def _tk_windows
@tk_windows
end
def _tk_table_list
@tk_table_list
end
def _add_new_tables
(@@TK_TABLE_LIST.size - @tk_table_list.size).times{
(tbl = {}).tainted? || tbl.taint
@tk_table_list << tbl
}
end
def _init_ip_env(script)
script.call(self)
end
def _add_tk_procs(name, args, body)
return if slave?
@interp._invoke('proc', name, args, body) if args && body
@interp._invoke('interp', 'slaves').split.each{|slave|
@interp._invoke('interp', 'alias', slave, name, '', name)
}
end
def _init_ip_internal(init_ip_env, add_tk_procs)
init_ip_env.each{|script| script.call(self)}
add_tk_procs.each{|name, args, body|
if master?
@interp._invoke('proc', name, args, body) if args && body
else
@set_alias_proc.call(name)
end
}
end
end
# class methods to treat tables
class MultiTkIp
def self.tk_cmd_tbl
@@TK_CMD_TBL
end
def self.tk_windows
__getip._tk_windows
end
def self.tk_object_table(id)
__getip._tk_table_list[id]
end
def self.create_table
#if __getip.slave?
# raise SecurityError, "slave-IP has no permission creating a new table"
#end
id = @@TK_TABLE_LIST.size
obj = Object.new
@@TK_TABLE_LIST << obj
obj.instance_eval <<-EOD
def self.method_missing(m, *args)
MultiTkIp.tk_object_table(#{id}).__send__(m, *args)
end
EOD
obj.freeze
@@IP_TABLE.each{|tg, ip| ip._add_new_tables }
return obj
end
def self.init_ip_env(script = Proc.new)
@@INIT_IP_ENV << script
@@IP_TABLE.each{|tg, ip|
ip._init_ip_env(script)
}
end
def self.add_tk_procs(name, args=nil, body=nil)
@@ADD_TK_PROCS << [name, args, body]
@@IP_TABLE.each{|tg, ip|
ip._add_tk_procs(name, args, body)
}
end
def self.init_ip_internal
__getip._init_ip_internal(@@INIT_IP_ENV, @@ADD_TK_PROCS)
end
end
# for callback operation
class MultiTkIp
def self.cb_entry_class
@@CB_ENTRY_CLASS
end
def self.get_cb_entry(cmd)
@@CB_ENTRY_CLASS.new(__getip, cmd).freeze
end
def cb_eval(cmd, *args)
#self.eval_callback{ TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *args)) }
#ret = self.eval_callback{ TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *args)) }
ret = self.eval_callback(*args){|safe, *params|
$SAFE=safe if $SAFE < safe
TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *params))
}
if ret.kind_of?(Exception)
raise ret
end
ret
end
end
# evaluate a procedure on the proper interpreter
class MultiTkIp
# instance method
def eval_proc_core(req_val, cmd, *args)
# check
unless cmd.kind_of?(Proc) || cmd.kind_of?(Method)
raise RuntimeError, "A Proc/Method object is expected for the 'cmd' argument"
end
# on IP thread
if @cmd_receiver == Thread.current ||
(!req_val && TclTkLib.mainloop_thread? != false) # callback
begin
ret = cmd.call(safe_level, *args)
rescue SystemExit => e
# exit IP
warn("Warning: "+ $! + " on " + self.inspect) if $DEBUG
begin
self._eval_without_enc('exit')
rescue Exception
end
self.delete
ret = nil
rescue Exception => e
if $DEBUG
warn("Warning: " + e.class.inspect +
((e.message.length > 0)? ' "' + e.message + '"': '') +
" on " + self.inspect)
end
=begin
begin
bt = _toUTF8(e.backtrace.join("\n"))
bt.instance_variable_set(:@encoding, 'utf-8')
rescue Exception
bt = e.backtrace.join("\n")
end
begin
@interp._set_global_var('errorInfo', bt)
rescue Exception
end
=end
ret = e
end
return ret
end
# send cmd to the proc-queue
unless req_val
begin
@cmd_queue.enq([nil, cmd, *args])
rescue Exception => e
# ignore
if $DEBUG
warn("Warning: " + e.class.inspect +
((e.message.length > 0)? ' "' + e.message + '"': '') +
" on " + self.inspect)
end
return e
end
return nil
end
# send and get return value by exception
begin
@cmd_queue.enq([Thread.current, cmd, *args])
Thread.stop
rescue MultiTkIp_OK => ret
# return value
return ret.value
rescue SystemExit => e
# exit IP
warn("Warning: " + $! + " on " + self.inspect) if $DEBUG
begin
self._eval_without_enc('exit')
rescue Exception
end
if !self.deleted? && !safe? && allow_ruby_exit?
self.delete
fail e
else
self.delete
end
rescue Exception => e
if $DEBUG
warn("Warning: " + e.class.inspect +
((e.message.length > 0)? ' "' + e.message + '"': '') +
" on " + self.inspect)
end
return e
end
return nil
end
private :eval_proc_core
def eval_callback(*args)
if block_given?
cmd = Proc.new
else
cmd = args.shift
end
current = Thread.current
backup_ip = current['callback_ip']
current['callback_ip'] = self
begin
eval_proc_core(false, cmd, *args)
ensure
current['callback_ip'] = backup_ip
end
end
def eval_proc(*args)
# The scope of the eval-block of 'eval_proc' method is different from
# the external. If you want to pass local values to the eval-block,
# use arguments of eval_proc method. They are passed to block-arguments.
if block_given?
cmd = Proc.new
else
unless (cmd = args.shift)
fail ArgumentError, "A Proc or Method object is expected for 1st argument"
end
end
if TclTkLib.mainloop_thread? == true
# call from eventloop
current = Thread.current
backup_ip = current['callback_ip']
current['callback_ip'] = self
begin
eval_proc_core(false,
proc{|safe, *params|
$SAFE=safe if $SAFE < safe
cmd.call(*params)
}, *args)
ensure
current['callback_ip'] = backup_ip
end
else
eval_proc_core(true,
proc{|safe, *params|
$SAFE=safe if $SAFE < safe
Thread.new(*params, &cmd).value
},
*args)
end
end
alias call eval_proc
def bg_eval_proc(*args)
if block_given?
cmd = Proc.new
else
unless (cmd = args.shift)
fail ArgumentError, "A Proc or Method object is expected for 1st argument"
end
end
Thread.new{
eval_proc(cmd, *args)
=begin
eval_proc_core(false,
proc{|safe, *params|
$SAFE=safe if $SAFE < safe
Thread.new(*params, &cmd).value
},
safe_level, *args)
=end
}
end
alias background_eval_proc bg_eval_proc
alias thread_eval_proc bg_eval_proc
alias bg_call bg_eval_proc
alias background_call bg_eval_proc
def eval_string(cmd, *eval_args)
# cmd string ==> proc
unless cmd.kind_of?(String)
raise RuntimeError, "A String object is expected for the 'cmd' argument"
end
eval_proc_core(true,
proc{|safe|
$SAFE=safe if $SAFE < safe
Kernel.eval(cmd, *eval_args)
})
end
alias eval_str eval_string
def bg_eval_string(*args)
# cmd string ==> proc
unless cmd.kind_of?(String)
raise RuntimeError, "A String object is expected for the 'cmd' argument"
end
Thread.new{
eval_proc_core(true,
proc{|safe|
$SAFE=safe if $SAFE < safe
Kernel.eval(cmd, *eval_args)
})
}
end
alias background_eval_string bg_eval_string
alias bg_eval_str bg_eval_string
alias background_eval_str bg_eval_string
end
class << MultiTkIp
# class method
def eval_proc(*args, &blk)
# class ==> interp object
__getip.eval_proc(*args, &blk)
end
alias call eval_proc
alias eval_string eval_proc
end
# event loop
# all master/slave IPs are controled by only one event-loop
class << MultiTkIp
def mainloop(check_root = true)
__getip.mainloop(check_root)
end
def mainloop_watchdog(check_root = true)
__getip.mainloop_watchdog(check_root)
end
def do_one_event(flag = TclTkLib::EventFlag::ALL)
__getip.do_one_event(flag)
end
def mainloop_abort_on_exception
# __getip.mainloop_abort_on_exception
TclTkLib.mainloop_abort_on_exception
end
def mainloop_abort_on_exception=(mode)
# __getip.mainloop_abort_on_exception=(mode)
TclTkLib.mainloop_abort_on_exception=(mode)
end
def set_eventloop_tick(tick)
__getip.set_eventloop_tick(tick)
end
def get_eventloop_tick
__getip.get_eventloop_tick
end
def set_no_event_wait(tick)
__getip.set_no_event_wait(tick)
end
def get_no_event_wait
__getip.get_no_event_wait
end
def set_eventloop_weight(loop_max, no_event_tick)
__getip.set_eventloop_weight(loop_max, no_event_tick)
end
def get_eventloop_weight
__getip.get_eventloop_weight
end
end
# class methods to delegate to TclTkIp
class << MultiTkIp
def method_missing(id, *args)
__getip.__send__(id, *args)
end
def make_safe
__getip.make_safe
end
def safe?
__getip.safe?
end
def safe_base?
begin
__getip.safe_base?
rescue
false
end
end
def allow_ruby_exit?
__getip.allow_ruby_exit?
end
def allow_ruby_exit= (mode)
__getip.allow_ruby_exit = mode
end
def delete
__getip.delete
end
def deleted?
__getip.deleted?
end
def has_mainwindow?
__getip.has_mainwindow?
end
def invalid_namespace?
__getip.invalid_namespace?
end
def abort(msg = nil)
__getip.abort(msg)
end
def exit(st = true)
__getip.exit(st)
end
def exit!(st = false)
__getip.exit!(st)
end
def restart(app_name = nil, keys = {})
init_ip_internal
__getip._invoke('set', 'argv0', app_name) if app_name
if keys.kind_of?(Hash)
__getip._invoke('set', 'argv', _keys2opts(keys))
end
__getip.restart
end
def _eval(str)
__getip._eval(str)
end
def _invoke(*args)
__getip._invoke(*args)
end
def _eval_without_enc(str)
__getip._eval_without_enc(str)
end
def _invoke_without_enc(*args)
__getip._invoke_without_enc(*args)
end
def _eval_with_enc(str)
__getip._eval_with_enc(str)
end
def _invoke_with_enc(*args)
__getip._invoke_with_enc(*args)
end
def _toUTF8(str, encoding=nil)
__getip._toUTF8(str, encoding)
end
def _fromUTF8(str, encoding=nil)
__getip._fromUTF8(str, encoding)
end
def _thread_vwait(var)
__getip._thread_vwait(var)
end
def _thread_tkwait(mode, target)
__getip._thread_tkwait(mode, target)
end
def _return_value
__getip._return_value
end
def _get_variable(var, flag)
__getip._get_variable(var, flag)
end
def _get_variable2(var, idx, flag)
__getip._get_variable2(var, idx, flag)
end
def _set_variable(var, value, flag)
__getip._set_variable(var, value, flag)
end
def _set_variable2(var, idx, value, flag)
__getip._set_variable2(var, idx, value, flag)
end
def _unset_variable(var, flag)
__getip._unset_variable(var, flag)
end
def _unset_variable2(var, idx, flag)
__getip._unset_variable2(var, idx, flag)
end
def _get_global_var(var)
__getip._get_global_var(var)
end
def _get_global_var2(var, idx)
__getip._get_global_var2(var, idx)
end
def _set_global_var(var, value)
__getip._set_global_var(var, value)
end
def _set_global_var2(var, idx, value)
__getip._set_global_var2(var, idx, value)
end
def _unset_global_var(var)
__getip._unset_global_var(var)
end
def _unset_global_var2(var, idx)
__getip._unset_global_var2(var, idx)
end
def _split_tklist(str)
__getip._split_tklist(str)
end
def _merge_tklist(*args)
__getip._merge_tklist(*args)
end
def _conv_listelement(arg)
__getip._conv_listelement(arg)
end
def _create_console
__getip._create_console
end
end
# wrap methods on TclTkLib : not permit calling TclTkLib module methods
class << TclTkLib
def mainloop(check_root = true)
MultiTkIp.mainloop(check_root)
end
def mainloop_watchdog(check_root = true)
MultiTkIp.mainloop_watchdog(check_root)
end
def do_one_event(flag = TclTkLib::EventFlag::ALL)
MultiTkIp.do_one_event(flag)
end
#def mainloop_abort_on_exception
# MultiTkIp.mainloop_abort_on_exception
#end
#def mainloop_abort_on_exception=(mode)
# MultiTkIp.mainloop_abort_on_exception=(mode)
#end
def set_eventloop_tick(tick)
MultiTkIp.set_eventloop_tick(tick)
end
def get_eventloop_tick
MultiTkIp.get_eventloop_tick
end
def set_no_event_wait(tick)
MultiTkIp.set_no_event_wait(tick)
end
def get_no_event_wait
MultiTkIp.get_no_event_wait
end
def set_eventloop_weight(loop_max, no_event_tick)
MultiTkIp.set_eventloop_weight(loop_max, no_event_tick)
end
def get_eventloop_weight
MultiTkIp.get_eventloop_weight
end
def restart(*args)
MultiTkIp.restart(*args)
end
def _merge_tklist(*args)
MultiTkIp._merge_tklist(*args)
end
def _conv_listelement(arg)
MultiTkIp._conv_listelement(arg)
end
end
# depend on TclTkIp
class MultiTkIp
def mainloop(check_root = true, restart_on_dead = true)
#return self if self.slave?
#return self if self != @@DEFAULT_MASTER
if self != @@DEFAULT_MASTER
if @wait_on_mainloop[0]
begin
@wait_on_mainloop[1] += 1
if $SAFE >= 4
_receiver_mainloop(check_root).join
else
@cmd_queue.enq([@system, 'call_mainloop',
Thread.current, check_root])
Thread.stop
end
rescue MultiTkIp_OK => ret
# return value
if ret.value.kind_of?(Thread)
return ret.value.value
else
return ret.value
end
rescue SystemExit => e
# exit IP
warn("Warning: " + $! + " on " + self.inspect) if $DEBUG
begin
self._eval_without_enc('exit')
rescue Exception
end
self.delete
rescue StandardError => e
if $DEBUG
warn("Warning: " + e.class.inspect +
((e.message.length > 0)? ' "' + e.message + '"': '') +
" on " + self.inspect)
end
return e
rescue Exception => e
return e
ensure
@wait_on_mainloop[1] -= 1
end
end
return
end
unless restart_on_dead
@wait_on_mainloop[1] += 1
=begin
begin
@interp.mainloop(check_root)
rescue StandardError => e
if $DEBUG
warn("Warning: " + e.class.inspect +
((e.message.length > 0)? ' "' + e.message + '"': '') +
" on " + self.inspect)
end
end
=end
begin
@interp.mainloop(check_root)
ensure
@wait_on_mainloop[1] -= 1
end
else
loop do
break unless self.alive?
if check_root
begin
break if TclTkLib.num_of_mainwindows == 0
rescue StandardError
break
end
end
break if @interp.deleted?
begin
@wait_on_mainloop[1] += 1
@interp.mainloop(check_root)
rescue StandardError => e
if TclTkLib.mainloop_abort_on_exception != nil
#STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect,
# " exception (ignore) : ", $!.message, "\n");
if $DEBUG
warn("Warning: Tk mainloop receives " << e.class.inspect <<
" exception (ignore) : " << e.message);
end
end
#raise e
rescue Exception => e
=begin
if TclTkLib.mainloop_abort_on_exception != nil
#STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect,
# " exception (ignore) : ", $!.message, "\n");
if $DEBUG
warn("Warning: Tk mainloop receives " << e.class.inspect <<
" exception (ignore) : " << e.message);
end
end
=end
raise e
ensure
@wait_on_mainloop[1] -= 1
Thread.pass # avoid eventloop conflict
end
end
end
self
end
def make_safe
@interp.make_safe
end
def safe?
@interp.safe?
end
def safe_base?
@safe_base
end
def allow_ruby_exit?
@interp.allow_ruby_exit?
end
def allow_ruby_exit= (mode)
@interp.allow_ruby_exit = mode
end
def delete
@slave_ip_tbl.each{|name, subip|
_destroy_slaves_of_slaveIP(subip)
=begin
begin
subip._invoke('destroy', '.') unless subip.deleted?
rescue Exception
end
=end
begin
# subip._eval_without_enc("foreach i [after info] {after cancel $i}")
unless subip.deleted?
after_ids = subip._eval_without_enc("after info")
subip._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
end
rescue Exception
end
# safe_base?
if @interp._eval_without_enc("catch {::safe::interpConfigure #{name}}") == '0'
begin
@interp._eval_without_enc("::safe::interpDelete #{name}")
rescue Exception
else
next if subip.deleted?
end
end
if subip.respond_to?(:safe_base?) && subip.safe_base? &&
!subip.deleted?
# do 'exit' to call the delete_hook procedure
begin
subip._eval_without_enc('exit')
rescue Exception
end
else
begin
subip.delete unless subip.deleted?
rescue Exception
end
end
}
begin
# @interp._eval_without_enc("foreach i [after info] {after cancel $i}")
after_ids = @interp._eval_without_enc("after info")
@interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
rescue Exception
end
begin
@interp._invoke('destroy', '.') unless @interp.deleted?
rescue Exception
end
if @safe_base && !@interp.deleted?
# do 'exit' to call the delete_hook procedure
@interp._eval_without_enc('exit')
end
@interp.delete
self
end
def deleted?
@interp.deleted?
end
def has_mainwindow?
@interp.has_mainwindow?
end
def invalid_namespace?
@interp.invalid_namespace?
end
def abort(msg = nil)
if master?
if msg
Kernel.abort(msg)
else
Kernel.abort
end
else
# ignore msg
delete
1
end
end
def exit(st = true)
if master?
Kernel.exit(st)
else
delete
st
end
end
def exit!(st = false)
if master? && !safe? && allow_ruby_exit?
Kernel.exit!(st)
else
delete
st
end
end
def restart(app_name = nil, keys = {})
_init_ip_internal(@@INIT_IP_ENV, @@ADD_TK_PROCS)
@interp._invoke('set', 'argv0', app_name) if app_name
if keys.kind_of?(Hash)
@interp._invoke('set', 'argv', _keys2opts(keys))
end
@interp.restart
end
def __eval(str)
@interp.__eval(str)
end
def __invoke(*args)
@interp.__invoke(*args)
end
def _eval(str)
@interp._eval(str)
end
def _invoke(*args)
@interp._invoke(*args)
end
def _eval_without_enc(str)
@interp._eval_without_enc(str)
end
def _invoke_without_enc(*args)
@interp._invoke_without_enc(*args)
end
def _eval_with_enc(str)
@interp._eval_with_enc(str)
end
def _invoke_with_enc(*args)
@interp._invoke_with_enc(*args)
end
def _toUTF8(str, encoding=nil)
@interp._toUTF8(str, encoding)
end
def _fromUTF8(str, encoding=nil)
@interp._fromUTF8(str, encoding)
end
def _thread_vwait(var)
@interp._thread_vwait(var)
end
def _thread_tkwait(mode, target)
@interp._thread_tkwait(mode, target)
end
def _return_value
@interp._return_value
end
def _get_variable(var, flag)
@interp._get_variable(var, flag)
end
def _get_variable2(var, idx, flag)
@interp._get_variable2(var, idx, flag)
end
def _set_variable(var, value, flag)
@interp._set_variable(var, value, flag)
end
def _set_variable2(var, idx, value, flag)
@interp._set_variable2(var, idx, value, flag)
end
def _unset_variable(var, flag)
@interp._unset_variable(var, flag)
end
def _unset_variable2(var, idx, flag)
@interp._unset_variable2(var, idx, flag)
end
def _get_global_var(var)
@interp._get_global_var(var)
end
def _get_global_var2(var, idx)
@interp._get_global_var2(var, idx)
end
def _set_global_var(var, value)
@interp._set_global_var(var, value)
end
def _set_global_var2(var, idx, value)
@interp._set_global_var2(var, idx, value)
end
def _unset_global_var(var)
@interp._unset_global_var(var)
end
def _unset_global_var2(var, idx)
@interp._unset_global_var2(var, idx)
end
def _split_tklist(str)
@interp._split_tklist(str)
end
def _merge_tklist(*args)
@interp._merge_tklist(*args)
end
def _conv_listelement(arg)
@interp._conv_listelement(arg)
end
end
# interp command support
class MultiTkIp
def _lst2ary(str)
return [] if str == ""
idx = str.index('{')
while idx and idx > 0 and str[idx-1] == ?\\
idx = str.index('{', idx+1)
end
return str.split unless idx
list = str[0,idx].split
str = str[idx+1..-1]
i = -1
brace = 1
str.each_byte {|c|
i += 1
brace += 1 if c == ?{
brace -= 1 if c == ?}
break if brace == 0
}
if i == 0
list.push ''
elsif str[0, i] == ' '
list.push ' '
else
list.push str[0..i-1]
end
#list += _lst2ary(str[i+1..-1])
list.concat(_lst2ary(str[i+1..-1]))
list
end
private :_lst2ary
def _slavearg(slave)
if slave.kind_of?(MultiTkIp)
slave.path
elsif slave.kind_of?(String)
slave
else
slave.to_s
end
end
private :_slavearg
def alias_info(slave, cmd_name)
_lst2ary(@interp._invoke('interp', 'alias', _slavearg(slave), cmd_name))
end
def self.alias_info(slave, cmd_name)
__getip.alias_info(slave, cmd_name)
end
def alias_delete(slave, cmd_name)
@interp._invoke('interp', 'alias', _slavearg(slave), cmd_name, '')
self
end
def self.alias_delete(slave, cmd_name)
__getip.alias_delete(slave, cmd_name)
self
end
def def_alias(slave, new_cmd, org_cmd, *args)
ret = @interp._invoke('interp', 'alias', _slavearg(slave), new_cmd,
'', org_cmd, *args)
(ret == new_cmd)? self: nil
end
def self.def_alias(slave, new_cmd, org_cmd, *args)
ret = __getip.def_alias(slave, new_cmd, org_cmd, *args)
(ret == new_cmd)? self: nil
end
def aliases(slave = '')
_lst2ary(@interp._invoke('interp', 'aliases', _slavearg(slave)))
end
def self.aliases(slave = '')
__getip.aliases(slave)
end
def delete_slaves(*args)
slaves = args.collect{|s| _slavearg(s)}
@interp._invoke('interp', 'delete', *slaves) if slaves.size > 0
self
end
def self.delete_slaves(*args)
__getip.delete_slaves(*args)
self
end
def exist?(slave = '')
ret = @interp._invoke('interp', 'exists', _slavearg(slave))
(ret == '1')? true: false
end
def self.exist?(slave = '')
__getip.exist?(slave)
end
def delete_cmd(slave, cmd)
slave_invoke = @interp._invoke('list', 'rename', cmd, '')
@interp._invoke('interp', 'eval', _slavearg(slave), slave_invoke)
self
end
def self.delete_cmd(slave, cmd)
__getip.delete_cmd(slave, cmd)
self
end
def expose_cmd(slave, cmd, aliasname = nil)
if aliasname
@interp._invoke('interp', 'expose', _slavearg(slave), cmd, aliasname)
else
@interp._invoke('interp', 'expose', _slavearg(slave), cmd)
end
self
end
def self.expose_cmd(slave, cmd, aliasname = nil)
__getip.expose_cmd(slave, cmd, aliasname)
self
end
def hide_cmd(slave, cmd, aliasname = nil)
if aliasname
@interp._invoke('interp', 'hide', _slavearg(slave), cmd, aliasname)
else
@interp._invoke('interp', 'hide', _slavearg(slave), cmd)
end
self
end
def self.hide_cmd(slave, cmd, aliasname = nil)
__getip.hide_cmd(slave, cmd, aliasname)
self
end
def hidden_cmds(slave = '')
_lst2ary(@interp._invoke('interp', 'hidden', _slavearg(slave)))
end
def self.hidden_cmds(slave = '')
__getip.hidden_cmds(slave)
end
def invoke_hidden(slave, cmd, *args)
if args[-1].kind_of?(Hash)
keys = _symbolkey2str(args.pop)
else
keys = []
end
if Tk::TCL_MAJOR_VERSION > 8 ||
(Tk::TCL_MAJOR_VERSION == 8 && Tk::TCL_MINOR_VERSION >= 5)
keys << '--'
end
keys << _slavearg(slave) << cmd
keys.concat(args)
@interp._invoke('interp', 'invokehidden', *keys)
end
def self.invoke_hidden(slave, cmd, *args)
__getip.invoke_hidden(slave, cmd, *args)
end
def invoke_hidden_on_global(slave, cmd, *args)
if args[-1].kind_of?(Hash)
keys = _symbolkey2str(args.pop)
else
keys = []
end
keys << '-global'
if Tk::TCL_MAJOR_VERSION > 8 ||
(Tk::TCL_MAJOR_VERSION == 8 && Tk::TCL_MINOR_VERSION >= 5)
keys << '--'
end
keys << _slavearg(slave) << cmd
keys.concat(args)
@interp._invoke('interp', 'invokehidden', *keys)
end
def self.invoke_hidden_on_global(slave, cmd, *args)
__getip.invoke_hidden_on_global(slave, cmd, *args)
end
def mark_trusted(slave = '')
@interp._invoke('interp', 'marktrusted', _slavearg(slave))
self
end
def self.mark_trusted(slave = '')
__getip.mark_trusted(slave)
self
end
def set_bgerror_handler(cmd = Proc.new, slave = nil, &b)
unless TkComm._callback_entry?(cmd)
if !slave && b
slave = cmd
cmd = Proc.new(&b)
end
end
slave = '' unless slave
@interp._invoke('interp', 'bgerror', _slavearg(slave), cmd)
end
def self.bgerror(cmd = Proc.new, slave = nil, &b)
__getip.bgerror(cmd, slave, &b)
end
def get_bgerror_handler(slave = '')
procedure(@interp._invoke('interp', 'bgerror', _slavearg(slave)))
end
def self.bgerror(slave = '')
__getip.bgerror(slave)
end
def set_limit(limit_type, slave = '', opts = {})
@interp._invoke('interp', 'limit', _slavearg(slave), limit_type, opts)
end
def self.set_limit(limit_type, slave = '', opts = {})
__getip.set_limit(limit_type, slave, opts)
end
def get_limit(limit_type, slave = '', slot = nil)
if slot
num_or_str(@interp._invoke('interp', 'limit', _slavearg(slave),
limit_type, slot))
else
l = @interp._split_tklist(@interp._invoke_without_enc('interp', 'limit',
_slavearg(slave),
limit_type))
l.map!{|s| _fromUTF8(s)}
r = {}
until l.empty?
key = l.shift[1..-1]
val = l.shift
val = num_or_str(val) if val
r[key] = val
end
r
end
end
def self.get_limit(limit_type, slave = '', slot = nil)
__getip.get_limit(limit_type, slave, slot)
end
def recursion_limit(slave = '', limit = None)
number(@interp._invoke('interp', 'recursionlimit',
_slavearg(slave), limit))
end
def self.recursion_limit(slave = '', limit = None)
__getip.recursion_limit(slave)
end
def alias_target(aliascmd, slave = '')
@interp._invoke('interp', 'target', _slavearg(slave), aliascmd)
end
def self.alias_target(aliascmd, slave = '')
__getip.alias_target(aliascmd, slave)
end
def share_stdin(dist, src = '')
@interp._invoke('interp', 'share', src, 'stdin', dist)
self
end
def self.share_stdin(dist, src = '')
__getip.share_stdin(dist, src)
self
end
def share_stdout(dist, src = '')
@interp._invoke('interp', 'share', src, 'stdout', dist)
self
end
def self.share_stdout(dist, src = '')
__getip.share_stdout(dist, src)
self
end
def share_stderr(dist, src = '')
@interp._invoke('interp', 'share', src, 'stderr', dist)
self
end
def self.share_stderr(dist, src = '')
__getip.share_stderr(dist, src)
self
end
def transfer_stdin(dist, src = '')
@interp._invoke('interp', 'transfer', src, 'stdin', dist)
self
end
def self.transfer_stdin(dist, src = '')
__getip.transfer_stdin(dist, src)
self
end
def transfer_stdout(dist, src = '')
@interp._invoke('interp', 'transfer', src, 'stdout', dist)
self
end
def self.transfer_stdout(dist, src = '')
__getip.transfer_stdout(dist, src)
self
end
def transfer_stderr(dist, src = '')
@interp._invoke('interp', 'transfer', src, 'stderr', dist)
self
end
def self.transfer_stderr(dist, src = '')
__getip.transfer_stderr(dist, src)
self
end
def share_stdio(dist, src = '')
@interp._invoke('interp', 'share', src, 'stdin', dist)
@interp._invoke('interp', 'share', src, 'stdout', dist)
@interp._invoke('interp', 'share', src, 'stderr', dist)
self
end
def self.share_stdio(dist, src = '')
__getip.share_stdio(dist, src)
self
end
def transfer_stdio(dist, src = '')
@interp._invoke('interp', 'transfer', src, 'stdin', dist)
@interp._invoke('interp', 'transfer', src, 'stdout', dist)
@interp._invoke('interp', 'transfer', src, 'stderr', dist)
self
end
def self.transfer_stdio(dist, src = '')
__getip.transfer_stdio(dist, src)
self
end
end
# Safe Base :: manipulating safe interpreter
class MultiTkIp
def safeip_configure(slot, value=None)
# use for '-noStatics' option ==> {statics=>false}
# for '-nestedLoadOk' option ==> {nested=>true}
if slot.kind_of?(Hash)
ip = MultiTkIp.__getip
ip._eval('::safe::interpConfigure ' + @ip_name + ' ' + _keys2opts(slot))
else
ip._eval('::safe::interpConfigure ' + @ip_name + ' ' +
"-#{slot} #{_get_eval_string(value)}")
end
self
end
def safeip_configinfo(slot = nil)
ip = MultiTkIp.__getip
ret = {}
if slot
conf = _lst2ary(ip._eval("::safe::interpConfigure " +
@ip_name + " -#{slot}"))
if conf[0] == '-deleteHook'
if conf[1] =~ /^rb_out\S* (c(_\d+_)?\d+)/
ret[conf[0][1..-1]] = MultiTkIp._tk_cmd_tbl[$1]
else
ret[conf[0][1..-1]] = conf[1]
end
else
ret[conf[0][1..-1]] = conf[1]
end
else
Hash[*_lst2ary(ip._eval("::safe::interpConfigure " +
@ip_name))].each{|k, v|
if k == '-deleteHook'
if v =~ /^rb_out\S* (c(_\d+_)?\d+)/
ret[k[1..-1]] = MultiTkIp._tk_cmd_tbl[$1]
else
ret[k[1..-1]] = v
end
else
ret[k[1..-1]] = v
end
}
end
ret
end
def safeip_delete
ip = MultiTkIp.__getip
ip._eval("::safe::interpDelete " + @ip_name)
end
def safeip_add_to_access_path(dir)
ip = MultiTkIp.__getip
ip._eval("::safe::interpAddToAccessPath #{@ip_name} #{dir}")
end
def safeip_find_in_access_path(dir)
ip = MultiTkIp.__getip
ip._eval("::safe::interpFindInAccessPath #{@ip_name} #{dir}")
end
def safeip_set_log_cmd(cmd = Proc.new)
ip = MultiTkIp.__getip
ip._eval("::safe::setLogCmd #{@ip_name} #{_get_eval_string(cmd)}")
end
end
# encoding convert
class MultiTkIp
def encoding
@interp.encoding
end
def encoding=(enc)
@interp.encoding = enc
end
def encoding_convertfrom(str, enc=None)
@interp.encoding_convertfrom(str, enc)
end
alias encoding_convert_from encoding_convertfrom
def encoding_convertto(str, enc=None)
@interp.encoding_convertto(str, enc)
end
alias encoding_convert_to encoding_convertto
end
# remove methods for security
class MultiTkIp
# undef_method :instance_eval
undef_method :instance_variable_get
undef_method :instance_variable_set
end
# end of MultiTkIp definition
# defend against modification
#MultiTkIp.freeze
#TclTkLib.freeze
########################################
# start Tk which depends on MultiTkIp
module TkCore
INTERP = MultiTkIp
end
require 'tk'