1#
2#               multi-tk.rb - supports multi Tk interpreters
3#                       by Hidetoshi NAGAI <nagai@ai.kyutech.ac.jp>
4
5require 'tcltklib'
6require 'tkutil'
7require 'thread'
8
9if defined? Tk
10  fail RuntimeError,"'multi-tk' library must be required before requiring 'tk'"
11end
12
13################################################
14# ignore exception on the mainloop?
15
16TclTkLib.mainloop_abort_on_exception = true
17# TclTkLib.mainloop_abort_on_exception = false
18# TclTkLib.mainloop_abort_on_exception = nil
19
20
21################################################
22# add ThreadGroup check to TclTkIp.new
23class << TclTkIp
24  alias __new__ new
25  private :__new__
26
27  def new(*args)
28    if Thread.current.group != ThreadGroup::Default
29      raise SecurityError, 'only ThreadGroup::Default can call TclTkIp.new'
30    end
31    obj = __new__(*args)
32    obj.instance_eval{
33      @force_default_encoding ||= TkUtil.untrust([false])
34      @encoding ||= TkUtil.untrust([nil])
35      def @encoding.to_s; self.join(nil); end
36    }
37    obj
38  end
39end
40
41
42################################################
43# exceptiopn to treat the return value from IP
44class MultiTkIp_OK < Exception
45  def self.send(thread, ret=nil)
46    thread.raise self.new(ret)
47  end
48
49  def initialize(ret=nil)
50    super('succeed')
51    @return_value = ret
52  end
53
54  attr_reader :return_value
55  alias value return_value
56end
57MultiTkIp_OK.freeze
58
59
60################################################
61# methods for construction
62class MultiTkIp
63  class Command_Queue < Queue
64    def initialize(interp)
65      @interp = interp
66      super()
67    end
68
69    def push(value)
70      if !@interp || @interp.deleted?
71        fail RuntimeError, "Tk interpreter is already deleted"
72      end
73      super(value)
74    end
75    alias << push
76    alias enq push
77
78    def close
79      @interp = nil
80    end
81  end
82  Command_Queue.freeze
83
84  BASE_DIR = File.dirname(__FILE__)
85
86  WITH_RUBY_VM  = Object.const_defined?(:RubyVM) && ::RubyVM.class == Class
87  WITH_ENCODING = defined?(::Encoding.default_external)
88  #WITH_ENCODING = Object.const_defined?(:Encoding) && ::Encoding.class == Class
89
90  (@@SLAVE_IP_ID = ['slave'.freeze, TkUtil.untrust('0')]).instance_eval{
91    @mutex = Mutex.new
92    def mutex; @mutex; end
93    freeze
94  }
95
96  @@IP_TABLE = TkUtil.untrust({}) unless defined?(@@IP_TABLE)
97
98  @@INIT_IP_ENV  = TkUtil.untrust([]) unless defined?(@@INIT_IP_ENV)  # table of Procs
99  @@ADD_TK_PROCS = TkUtil.untrust([]) unless defined?(@@ADD_TK_PROCS) # table of [name, args, body]
100
101  @@TK_TABLE_LIST = TkUtil.untrust([]) unless defined?(@@TK_TABLE_LIST)
102
103  unless defined?(@@TK_CMD_TBL)
104    @@TK_CMD_TBL = TkUtil.untrust(Object.new)
105
106    # @@TK_CMD_TBL.instance_variable_set('@tbl', {}.taint)
107    tbl_obj = TkUtil.untrust(Hash.new{|hash,key|
108                               fail IndexError, "unknown command ID '#{key}'"
109                             })
110    @@TK_CMD_TBL.instance_variable_set('@tbl', tbl_obj)
111
112    class << @@TK_CMD_TBL
113      allow = [
114        '__send__', '__id__', 'freeze', 'inspect', 'kind_of?', 'object_id',
115        '[]', '[]=', 'delete', 'each', 'has_key?'
116      ]
117      instance_methods.each{|m| undef_method(m) unless allow.index(m.to_s)}
118
119      def kind_of?(klass)
120        @tbl.kind_of?(klass)
121      end
122
123      def inspect
124        if Thread.current.group == ThreadGroup::Default
125          @tbl.inspect
126        else
127          ip = MultiTkIp.__getip
128          @tbl.reject{|idx, ent| ent.respond_to?(:ip) && ent.ip != ip}.inspect
129        end
130      end
131
132      def [](idx)
133        return unless (ent = @tbl[idx])
134        if Thread.current.group == ThreadGroup::Default
135          ent
136        elsif ent.respond_to?(:ip)
137          (ent.ip == MultiTkIp.__getip)? ent: nil
138        else
139          ent
140        end
141      end
142
143      def []=(idx,val)
144        if self.has_key?(idx) && Thread.current.group != ThreadGroup::Default
145          fail SecurityError,"cannot change the entried command"
146        end
147        @tbl[idx] = val
148      end
149
150      def delete(idx, &blk)
151        # if gets an entry, is permited to delete
152        if self[idx]
153          @tbl.delete(idx)
154        elsif blk
155          blk.call(idx)
156        else
157          nil
158        end
159      end
160
161      def each(&blk)
162        if Thread.current.group == ThreadGroup::Default
163          @tbl.each(&blk)
164        else
165          ip = MultiTkIp.__getip
166          @tbl.each{|idx, ent|
167            blk.call(idx, ent) unless ent.respond_to?(:ip) && ent.ip != ip
168          }
169        end
170        self
171      end
172
173      def has_key?(k)
174        @tbl.has_key?(k)
175      end
176      alias include? has_key?
177      alias key? has_key?
178      alias member? has_key?
179    end
180
181    @@TK_CMD_TBL.freeze
182  end
183
184  ######################################
185
186  @@CB_ENTRY_CLASS = Class.new(TkCallbackEntry){
187    def initialize(ip, cmd)
188      @ip = ip
189      @safe = safe = $SAFE
190      # @cmd = cmd
191      cmd = MultiTkIp._proc_on_safelevel(&cmd)
192      @cmd = proc{|*args| cmd.call(safe, *args)}
193      self.freeze
194    end
195    attr_reader :ip, :cmd
196    def inspect
197      cmd.inspect
198    end
199    def call(*args)
200      unless @ip.deleted?
201        current = Thread.current
202        backup_ip = current[:callback_ip]
203        current[:callback_ip] = @ip
204        begin
205          ret = @ip.cb_eval(@cmd, *args)
206          fail ret if ret.kind_of?(Exception)
207          ret
208        rescue TkCallbackBreak, TkCallbackContinue => e
209          fail e
210	rescue SecurityError => e
211          # in 'exit', 'exit!', and 'abort' : security error --> delete IP
212          if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/
213	    @ip.delete
214          elsif @ip.safe?
215	    if @ip.respond_to?(:cb_error)
216              @ip.cb_error(e)
217            else
218              nil # ignore
219            end
220          else
221            fail e
222          end
223        rescue Exception => e
224          fail e if e.message =~ /^TkCallback/
225
226          if @ip.safe?
227	    if @ip.respond_to?(:cb_error)
228              @ip.cb_error(e)
229            else
230              nil # ignore
231            end
232          else
233            fail e
234          end
235        ensure
236          current[:callback_ip] = backup_ip
237        end
238      end
239    end
240  }.freeze
241
242  ######################################
243
244  def _keys2opts(src_keys)
245    return nil if src_keys == nil
246    keys = {}; src_keys.each{|k, v| keys[k.to_s] = v}
247    #keys.collect{|k,v| "-#{k} #{v}"}.join(' ')
248    keys.collect{|k,v| "-#{k} #{TclTkLib._conv_listelement(TkComm::_get_eval_string(v))}"}.join(' ')
249  end
250  private :_keys2opts
251
252  def _check_and_return(thread, exception, wait=0)
253    unless thread
254      unless exception.kind_of?(MultiTkIp_OK)
255        msg = "#{exception.class}: #{exception.message}"
256
257        if @interp.deleted?
258          warn("Warning (#{self}): " + msg)
259          return nil
260        end
261
262        if safe?
263          warn("Warning (#{self}): " + msg) if $DEBUG
264          return nil
265        end
266
267        begin
268          @interp._eval_without_enc(@interp._merge_tklist('bgerror', msg))
269        rescue Exception => e
270          warn("Warning (#{self}): " + msg)
271        end
272      end
273      return nil
274    end
275
276    if wait == 0
277      # no wait
278      Thread.pass
279      if thread.stop?
280        thread.raise exception
281      end
282      return thread
283    end
284
285    # wait to stop the caller thread
286    wait.times{
287      if thread.stop?
288        # ready to send exception
289        thread.raise exception
290        return thread
291      end
292
293      # wait
294      Thread.pass
295    }
296
297    # unexpected error
298    thread.raise RuntimeError, "the thread may not wait for the return value"
299    return thread
300  end
301
302  ######################################
303
304  def set_cb_error(cmd = Proc.new)
305    @cb_error_proc[0] = cmd
306  end
307
308  def cb_error(e)
309    if @cb_error_proc[0].respond_to?(:call)
310      @cb_error_proc[0].call(e)
311    end
312  end
313
314  ######################################
315
316  def set_safe_level(safe)
317    if safe > @safe_level[0]
318      @safe_level[0] = safe
319      @cmd_queue.enq([@system, 'set_safe_level', safe])
320    end
321    @safe_level[0]
322  end
323  def safe_level=(safe)
324    set_safe_level(safe)
325  end
326  def self.set_safe_level(safe)
327    __getip.set_safe_level(safe)
328  end
329  def self.safe_level=(safe)
330    self.set_safe_level(safe)
331  end
332  def safe_level
333    @safe_level[0]
334  end
335  def self.safe_level
336    __getip.safe_level
337  end
338
339  def wait_on_mainloop?
340    @wait_on_mainloop[0]
341  end
342  def wait_on_mainloop=(bool)
343    @wait_on_mainloop[0] = bool
344  end
345
346  def running_mainloop?
347    @wait_on_mainloop[1] > 0
348  end
349
350  def _destroy_slaves_of_slaveIP(ip)
351    unless ip.deleted?
352      # ip._split_tklist(ip._invoke('interp', 'slaves')).each{|name|
353      ip._split_tklist(ip._invoke_without_enc('interp', 'slaves')).each{|name|
354        name = _fromUTF8(name)
355        begin
356          # ip._eval_without_enc("#{name} eval {foreach i [after info] {after cancel $i}}")
357          after_ids = ip._eval_without_enc("#{name} eval {after info}")
358          ip._eval_without_enc("#{name} eval {foreach i {#{after_ids}} {after cancel $i}}")
359        rescue Exception
360        end
361        begin
362          # ip._invoke('interp', 'eval', name, 'destroy', '.')
363          ip._invoke(name, 'eval', 'destroy', '.')
364        rescue Exception
365        end
366
367        # safe_base?
368        if ip._eval_without_enc("catch {::safe::interpConfigure #{name}}") == '0'
369          begin
370            ip._eval_without_enc("::safe::interpDelete #{name}")
371          rescue Exception
372          end
373        end
374=begin
375        if ip._invoke('interp', 'exists', name) == '1'
376          begin
377            ip._invoke(name, 'eval', 'exit')
378          rescue Exception
379          end
380        end
381=end
382        unless ip.deleted?
383          if ip._invoke('interp', 'exists', name) == '1'
384            begin
385              ip._invoke('interp', 'delete', name)
386            rescue Exception
387            end
388          end
389        end
390      }
391    end
392  end
393
394  def _receiver_eval_proc_core(safe_level, thread, cmd, *args)
395    begin
396      #ret = proc{$SAFE = safe_level; cmd.call(*args)}.call
397      #ret = cmd.call(safe_level, *args)
398      normal_ret = false
399      ret = catch(:IRB_EXIT) do  # IRB hack
400        retval = cmd.call(safe_level, *args)
401        normal_ret = true
402        retval
403      end
404      unless normal_ret
405        # catch IRB_EXIT
406        exit(ret)
407      end
408      ret
409    rescue SystemExit => e
410      # delete IP
411      unless @interp.deleted?
412        @slave_ip_tbl.each{|name, subip|
413          _destroy_slaves_of_slaveIP(subip)
414          begin
415            # subip._eval_without_enc("foreach i [after info] {after cancel $i}")
416            after_ids = subip._eval_without_enc("after info")
417            subip._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
418          rescue Exception
419          end
420=begin
421          begin
422            subip._invoke('destroy', '.') unless subip.deleted?
423          rescue Exception
424          end
425=end
426          # safe_base?
427          if @interp._eval_without_enc("catch {::safe::interpConfigure #{name}}") == '0'
428            begin
429              @interp._eval_without_enc("::safe::interpDelete #{name}")
430            rescue Exception
431            else
432              next if subip.deleted?
433            end
434          end
435          if subip.respond_to?(:safe_base?) && subip.safe_base? &&
436              !subip.deleted?
437            # do 'exit' to call the delete_hook procedure
438            begin
439              subip._eval_without_enc('exit')
440            rescue Exception
441            end
442          else
443            begin
444              subip.delete unless subip.deleted?
445            rescue Exception
446            end
447          end
448        }
449
450        begin
451          # @interp._eval_without_enc("foreach i [after info] {after cancel $i}")
452          after_ids = @interp._eval_without_enc("after info")
453          @interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
454        rescue Exception
455        end
456        begin
457          @interp._invoke('destroy', '.') unless @interp.deleted?
458        rescue Exception
459        end
460        if @safe_base && !@interp.deleted?
461          # do 'exit' to call the delete_hook procedure
462          @interp._eval_without_enc('exit')
463        else
464          @interp.delete unless @interp.deleted?
465        end
466      end
467
468      if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/
469        _check_and_return(thread, MultiTkIp_OK.new($3 == 'exit'))
470      else
471        _check_and_return(thread, MultiTkIp_OK.new(nil))
472      end
473
474      # if master? && !safe? && allow_ruby_exit?
475      if !@interp.deleted? && master? && !safe? && allow_ruby_exit?
476=begin
477        ObjectSpace.each_object(TclTkIp){|obj|
478          obj.delete unless obj.deleted?
479        }
480=end
481        #exit(e.status)
482        fail e
483      end
484      # break
485
486    rescue SecurityError => e
487      # in 'exit', 'exit!', and 'abort' : security error --> delete IP
488      if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/
489        ret = ($3 == 'exit')
490        unless @interp.deleted?
491          @slave_ip_tbl.each{|name, subip|
492            _destroy_slaves_of_slaveIP(subip)
493            begin
494              # subip._eval_without_enc("foreach i [after info] {after cancel $i}")
495              after_ids = subip._eval_without_enc("after info")
496              subip._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
497            rescue Exception
498            end
499=begin
500            begin
501              subip._invoke('destroy', '.') unless subip.deleted?
502            rescue Exception
503            end
504=end
505            # safe_base?
506            if @interp._eval_without_enc("catch {::safe::interpConfigure #{name}}") == '0'
507              begin
508                @interp._eval_without_enc("::safe::interpDelete #{name}")
509              rescue Exception
510              else
511                next if subip.deleted?
512              end
513            end
514            if subip.respond_to?(:safe_base?) && subip.safe_base? &&
515                !subip.deleted?
516              # do 'exit' to call the delete_hook procedure
517              begin
518                subip._eval_without_enc('exit')
519              rescue Exception
520              end
521            else
522              begin
523                subip.delete unless subip.deleted?
524              rescue Exception
525              end
526            end
527          }
528
529          begin
530            # @interp._eval_without_enc("foreach i [after info] {after cancel $i}")
531            after_ids = @interp._eval_without_enc("after info")
532            @interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
533          rescue Exception
534          end
535=begin
536          begin
537            @interp._invoke('destroy', '.') unless @interp.deleted?
538          rescue Exception
539          end
540=end
541          if @safe_base && !@interp.deleted?
542            # do 'exit' to call the delete_hook procedure
543            @interp._eval_without_enc('exit')
544          else
545            @interp.delete unless @interp.deleted?
546          end
547        end
548        _check_and_return(thread, MultiTkIp_OK.new(ret))
549        # break
550
551      else
552        # raise security error
553        _check_and_return(thread, e)
554      end
555
556    rescue Exception => e
557      # raise exception
558      begin
559        bt = _toUTF8(e.backtrace.join("\n"))
560        if MultiTkIp::WITH_ENCODING
561          bt.force_encoding('utf-8')
562        else
563          bt.instance_variable_set(:@encoding, 'utf-8')
564        end
565      rescue Exception
566        bt = e.backtrace.join("\n")
567      end
568      begin
569        @interp._set_global_var('errorInfo', bt)
570      rescue Exception
571      end
572      _check_and_return(thread, e)
573
574    else
575      # no exception
576      _check_and_return(thread, MultiTkIp_OK.new(ret))
577    end
578  end
579
580  def _receiver_eval_proc(last_thread, safe_level, thread, cmd, *args)
581    if thread
582      Thread.new{
583        last_thread.join if last_thread
584        unless @interp.deleted?
585          _receiver_eval_proc_core(safe_level, thread, cmd, *args)
586        end
587      }
588    else
589      Thread.new{
590        unless  @interp.deleted?
591          _receiver_eval_proc_core(safe_level, thread, cmd, *args)
592        end
593      }
594      last_thread
595    end
596  end
597
598  private :_receiver_eval_proc, :_receiver_eval_proc_core
599
600  def _receiver_mainloop(check_root)
601    if @evloop_thread[0] && @evloop_thread[0].alive?
602      @evloop_thread[0]
603    else
604      @evloop_thread[0] = Thread.new{
605	while !@interp.deleted?
606	  #if check_root
607	  #  inf = @interp._invoke_without_enc('info', 'command', '.')
608	  #  break if !inf.kind_of?(String) || inf != '.'
609	  #end
610          break if check_root && !@interp.has_mainwindow?
611	  sleep 0.5
612	end
613      }
614      @evloop_thread[0]
615    end
616  end
617
618  def _create_receiver_and_watchdog(lvl = $SAFE)
619    lvl = $SAFE if lvl < $SAFE
620
621    # command-procedures receiver
622    receiver = Thread.new(lvl){|safe_level|
623      last_thread = {}
624
625      loop do
626        break if @interp.deleted?
627        thread, cmd, *args = @cmd_queue.deq
628        if thread == @system
629          # control command
630          case cmd
631          when 'set_safe_level'
632            begin
633              safe_level = args[0] if safe_level < args[0]
634            rescue Exception
635            end
636          when 'call_mainloop'
637            thread = args.shift
638            _check_and_return(thread,
639                              MultiTkIp_OK.new(_receiver_mainloop(*args)))
640          else
641            # ignore
642          end
643
644        else
645          # procedure
646          last_thread[thread] = _receiver_eval_proc(last_thread[thread],
647						    safe_level, thread,
648						    cmd, *args)
649        end
650      end
651    }
652
653    # watchdog of receiver
654    watchdog = Thread.new{
655      begin
656        loop do
657          sleep 1
658          if @interp.deleted?
659            receiver.kill
660            @cmd_queue.close
661          end
662          break unless receiver.alive?
663        end
664      rescue Exception
665        # ignore all kind of Exception
666      end
667
668      # receiver is dead
669      retry_count = 3
670      loop do
671        Thread.pass
672        begin
673          thread, cmd, *args = @cmd_queue.deq(true) # non-block
674        rescue ThreadError
675          # queue is empty
676          retry_count -= 1
677          break if retry_count <= 0
678          sleep 0.5
679          retry
680        end
681        next unless thread
682        if thread.alive?
683          if @interp.deleted?
684            thread.raise RuntimeError, 'the interpreter is already deleted'
685          else
686            thread.raise RuntimeError,
687              'the interpreter no longer receives command procedures'
688          end
689        end
690      end
691    }
692
693    # return threads
694    [receiver, watchdog]
695  end
696  private :_check_and_return, :_create_receiver_and_watchdog
697
698  ######################################
699
700  unless self.const_defined? :RUN_EVENTLOOP_ON_MAIN_THREAD
701    ### Ruby 1.9 !!!!!!!!!!!!!!!!!!!!!!!!!!
702    RUN_EVENTLOOP_ON_MAIN_THREAD = false
703  end
704
705  if self.const_defined? :DEFAULT_MASTER_NAME
706    name = DEFAULT_MASTER_NAME.to_s
707  else
708    name = nil
709  end
710  if self.const_defined?(:DEFAULT_MASTER_OPTS) &&
711      DEFAULT_MASTER_OPTS.kind_of?(Hash)
712    keys = DEFAULT_MASTER_OPTS
713  else
714    keys = {}
715  end
716
717  @@DEFAULT_MASTER = self.allocate
718  @@DEFAULT_MASTER.instance_eval{
719    @tk_windows = TkUtil.untrust({})
720
721    @tk_table_list = TkUtil.untrust([])
722
723    @slave_ip_tbl = TkUtil.untrust({})
724
725    @slave_ip_top = TkUtil.untrust({})
726
727    @evloop_thread = TkUtil.untrust([])
728
729    unless keys.kind_of? Hash
730      fail ArgumentError, "expecting a Hash object for the 2nd argument"
731    end
732
733    if !WITH_RUBY_VM || RUN_EVENTLOOP_ON_MAIN_THREAD ### check Ruby 1.9 !!!!!!!
734      @interp = TclTkIp.new(name, _keys2opts(keys))
735    else ### Ruby 1.9 !!!!!!!!!!!
736      @interp_thread = Thread.new{
737        current = Thread.current
738        begin
739          current[:interp] = interp = TclTkIp.new(name, _keys2opts(keys))
740        rescue e
741          current[:interp] = e
742          raise e
743        end
744        #sleep
745        current[:mutex] = mutex = Mutex.new
746        current[:root_check] = cond_var = ConditionVariable.new
747
748        status = [nil]
749        def status.value
750          self[0]
751        end
752        def status.value=(val)
753          self[0] = val
754        end
755        current[:status] = status
756
757        begin
758          begin
759            #TclTkLib.mainloop_abort_on_exception = false
760            #Thread.current[:status].value = TclTkLib.mainloop(true)
761            interp.mainloop_abort_on_exception = true
762            current[:status].value = interp.mainloop(true)
763          rescue SystemExit=>e
764            current[:status].value = e
765          rescue Exception=>e
766            current[:status].value = e
767            retry if interp.has_mainwindow?
768          ensure
769            mutex.synchronize{ cond_var.broadcast }
770          end
771
772          #Thread.current[:status].value = TclTkLib.mainloop(false)
773          current[:status].value = interp.mainloop(false)
774
775        ensure
776          # interp must be deleted before the thread for interp is dead.
777          # If not, raise Tcl_Panic on Tcl_AsyncDelete because async handler
778          # deleted by the wrong thread.
779          interp.delete
780        end
781      }
782      until @interp_thread[:interp]
783        Thread.pass
784      end
785      # INTERP_THREAD.run
786      raise @interp_thread[:interp] if @interp_thread[:interp].kind_of? Exception
787      @interp = @interp_thread[:interp]
788
789      # delete the interpreter and kill the eventloop thread at exit
790      interp = @interp
791      interp_thread = @interp_thread
792      END{
793        if interp_thread.alive?
794          interp.delete
795          interp_thread.kill
796        end
797      }
798
799      def self.mainloop(check_root = true)
800        begin
801          TclTkLib.set_eventloop_window_mode(true)
802          @interp_thread.value
803        ensure
804          TclTkLib.set_eventloop_window_mode(false)
805        end
806      end
807    end
808
809    @interp.instance_eval{
810      @force_default_encoding ||= TkUtil.untrust([false])
811      @encoding ||= TkUtil.untrust([nil])
812      def @encoding.to_s; self.join(nil); end
813    }
814
815    @ip_name = nil
816
817    @callback_status = TkUtil.untrust([])
818
819    @system = Object.new
820
821    @wait_on_mainloop = TkUtil.untrust([true, 0])
822
823    @threadgroup  = Thread.current.group
824
825    @safe_base = false
826
827    @safe_level = [$SAFE]
828
829    @cmd_queue = MultiTkIp::Command_Queue.new(@interp)
830
831    @cmd_receiver, @receiver_watchdog = _create_receiver_and_watchdog(@safe_level[0])
832
833    @threadgroup.add @cmd_receiver
834    @threadgroup.add @receiver_watchdog
835
836    # NOT enclose @threadgroup for @@DEFAULT_MASTER
837
838    @@IP_TABLE[ThreadGroup::Default] = self
839    @@IP_TABLE[@threadgroup] = self
840
841    #################################
842
843    @pseudo_toplevel = [false, nil]
844
845    def self.__pseudo_toplevel
846      Thread.current.group == ThreadGroup::Default &&
847        MultiTkIp.__getip == @@DEFAULT_MASTER &&
848        self.__pseudo_toplevel_evaluable? && @pseudo_toplevel[1]
849    end
850
851    def self.__pseudo_toplevel=(m)
852      unless (Thread.current.group == ThreadGroup::Default &&
853                MultiTkIp.__getip == @@DEFAULT_MASTER)
854        fail SecurityError, "no permission to manipulate"
855      end
856
857      # if m.kind_of?(Module) && m.respond_to?(:pseudo_toplevel_evaluable?)
858      if m.respond_to?(:pseudo_toplevel_evaluable?)
859        @pseudo_toplevel[0] = true
860        @pseudo_toplevel[1] = m
861      else
862        fail ArgumentError, 'fail to set pseudo-toplevel'
863      end
864      self
865    end
866
867    def self.__pseudo_toplevel_evaluable?
868      begin
869        @pseudo_toplevel[0] && @pseudo_toplevel[1].pseudo_toplevel_evaluable?
870      rescue Exception
871        false
872      end
873    end
874
875    def self.__pseudo_toplevel_evaluable=(mode)
876      unless (Thread.current.group == ThreadGroup::Default &&
877                MultiTkIp.__getip == @@DEFAULT_MASTER)
878        fail SecurityError, "no permission to manipulate"
879      end
880
881      @pseudo_toplevel[0] = (mode)? true: false
882    end
883
884    #################################
885
886    @assign_request = Class.new(Exception){
887      def self.new(target, ret)
888        obj = super()
889        obj.target = target
890        obj.ret = ret
891        obj
892      end
893      attr_accessor :target, :ret
894    }
895
896    @assign_thread = Thread.new{
897      loop do
898        begin
899          Thread.stop
900        rescue @assign_request=>req
901          begin
902            req.ret[0] = req.target.instance_eval{
903              @cmd_receiver, @receiver_watchdog =
904                _create_receiver_and_watchdog(@safe_level[0])
905              @threadgroup.add @cmd_receiver
906              @threadgroup.add @receiver_watchdog
907              @threadgroup.enclose
908              true
909            }
910          rescue Exception=>e
911            begin
912              req.ret[0] = e
913            rescue Exception
914              # ignore
915            end
916          end
917        rescue Exception
918          # ignore
919        end
920      end
921    }
922
923    def self.assign_receiver_and_watchdog(target)
924      ret = [nil]
925      @assign_thread.raise(@assign_request.new(target, ret))
926      while ret[0] == nil
927        unless @assign_thread.alive?
928          raise RuntimeError, 'lost the thread to assign a receiver and a watchdog thread'
929        end
930      end
931      if ret[0].kind_of?(Exception)
932        raise ret[0]
933      else
934        ret[0]
935      end
936    end
937
938    #################################
939
940    @init_ip_env_queue = Queue.new
941    Thread.new{
942      current = Thread.current
943      loop {
944        mtx, cond, ret, table, script = @init_ip_env_queue.deq
945        begin
946          ret[0] = table.each{|tg, ip| ip._init_ip_env(script) }
947        rescue Exception => e
948          ret[0] = e
949        ensure
950          mtx.synchronize{ cond.signal }
951        end
952        mtx = cond = ret = table = script = nil  # clear variables for GC
953      }
954    }
955
956    def self.__init_ip_env__(table, script)
957      ret = []
958      mtx  = (Thread.current[:MultiTk_ip_Mutex] ||= Mutex.new)
959      cond = (Thread.current[:MultiTk_ip_CondVar] ||= ConditionVariable.new)
960      mtx.synchronize{
961        @init_ip_env_queue.enq([mtx, cond, ret, table, script])
962        cond.wait(mtx)
963      }
964      if ret[0].kind_of?(Exception)
965        raise ret[0]
966      else
967        ret[0]
968      end
969    end
970
971    #################################
972
973    class << self
974      undef :instance_eval
975    end
976  }
977
978  @@DEFAULT_MASTER.freeze # defend against modification
979
980  ######################################
981
982  def self.inherited(subclass)
983    # trust if on ThreadGroup::Default or @@DEFAULT_MASTER's ThreadGroup
984    if @@IP_TABLE[Thread.current.group] == @@DEFAULT_MASTER
985      begin
986        class << subclass
987          self.methods.each{|m|
988            name = m.to_s
989            begin
990              unless name == '__id__' || name == '__send__' || name == 'freeze'
991                undef_method(m)
992              end
993            rescue Exception
994              # ignore all exceptions
995            end
996          }
997        end
998      ensure
999        subclass.freeze
1000        fail SecurityError,
1001          "cannot create subclass of MultiTkIp on a untrusted ThreadGroup"
1002      end
1003    end
1004  end
1005
1006  ######################################
1007
1008  @@SAFE_OPT_LIST = [
1009    'accessPath'.freeze,
1010    'statics'.freeze,
1011    'nested'.freeze,
1012    'deleteHook'.freeze
1013  ].freeze
1014
1015  def _parse_slaveopts(keys)
1016    name = nil
1017    safe = false
1018    safe_opts = {}
1019    tk_opts   = {}
1020
1021    keys.each{|k,v|
1022      k_str = k.to_s
1023      if k_str == 'name'
1024        name = v
1025      elsif k_str == 'safe'
1026        safe = v
1027      elsif @@SAFE_OPT_LIST.member?(k_str)
1028        safe_opts[k_str] = v
1029      else
1030        tk_opts[k_str] = v
1031      end
1032    }
1033
1034    if keys['without_tk'] || keys[:without_tk]
1035      [name, safe, safe_opts, nil]
1036    else
1037      [name, safe, safe_opts, tk_opts]
1038    end
1039  end
1040  private :_parse_slaveopts
1041
1042  def _create_slave_ip_name
1043    @@SLAVE_IP_ID.mutex.synchronize{
1044      name = @@SLAVE_IP_ID.join('')
1045      @@SLAVE_IP_ID[1].succ!
1046      name.freeze
1047    }
1048  end
1049  private :_create_slave_ip_name
1050
1051  ######################################
1052
1053  def __check_safetk_optkeys(optkeys)
1054    # based on 'safetk.tcl'
1055    new_keys = {}
1056    optkeys.each{|k,v| new_keys[k.to_s] = v}
1057
1058    # check 'display'
1059    if !new_keys.key?('display')
1060      begin
1061        #new_keys['display'] = @interp._invoke('winfo screen .')
1062        new_keys['display'] = @interp._invoke('winfo', 'screen', '.')
1063      rescue
1064        if ENV[DISPLAY]
1065          new_keys['display'] = ENV[DISPLAY]
1066        elsif !new_keys.key?('use')
1067          warn "Warning: no screen info or ENV[DISPLAY], so use ':0.0'"
1068          new_keys['display'] = ':0.0'
1069        end
1070      end
1071    end
1072
1073    # check 'use'
1074    if new_keys.key?('use')
1075      # given 'use'
1076      case new_keys['use']
1077      when TkWindow
1078        new_keys['use'] = TkWinfo.id(new_keys['use'])
1079        #assoc_display = @interp._eval('winfo screen .')
1080        assoc_display = @interp._invoke('winfo', 'screen', '.')
1081      when /^\..*/
1082        new_keys['use'] = @interp._invoke('winfo', 'id', new_keys['use'])
1083        assoc_display = @interp._invoke('winfo', 'screen', new_keys['use'])
1084      else
1085        begin
1086          pathname = @interp._invoke('winfo', 'pathname', new_keys['use'])
1087          assoc_display = @interp._invoke('winfo', 'screen', pathname)
1088        rescue
1089          assoc_display = new_keys['display']
1090        end
1091      end
1092
1093      # match display?
1094      if assoc_display != new_keys['display']
1095        if optkeys.key?(:display) || optkeys.key?('display')
1096          fail RuntimeError,
1097            "conflicting 'display'=>#{new_keys['display']} " +
1098            "and display '#{assoc_display}' on 'use'=>#{new_keys['use']}"
1099        else
1100          new_keys['display'] = assoc_display
1101        end
1102      end
1103    end
1104
1105    # return
1106    new_keys
1107  end
1108  private :__check_safetk_optkeys
1109
1110  def __create_safetk_frame(slave_ip, slave_name, app_name, keys)
1111    # display option is used by ::safe::loadTk
1112    loadTk_keys = {}
1113    loadTk_keys['display'] = keys['display']
1114    dup_keys = keys.dup
1115
1116    # keys for toplevel : allow followings
1117    toplevel_keys = {}
1118    ['height', 'width', 'background', 'menu'].each{|k|
1119      toplevel_keys[k] = dup_keys.delete(k) if dup_keys.key?(k)
1120    }
1121    toplevel_keys['classname'] = 'SafeTk'
1122    toplevel_keys['screen'] = dup_keys.delete('display')
1123
1124    # other keys used by pack option of container frame
1125
1126    # create toplevel widget
1127    begin
1128      top = TkToplevel.new(toplevel_keys)
1129    rescue NameError => e
1130      fail e unless @interp.safe?
1131      fail SecurityError, "unable create toplevel on the safe interpreter"
1132    end
1133    msg = "Untrusted Ruby/Tk applet (#{slave_name})"
1134    if app_name.kind_of?(String)
1135      top.title "#{app_name} (#{slave_name})"
1136    else
1137      top.title msg
1138    end
1139
1140    # procedure to delete slave interpreter
1141    slave_delete_proc = proc{
1142      unless slave_ip.deleted?
1143        #if slave_ip._invoke('info', 'command', '.') != ""
1144        #  slave_ip._invoke('destroy', '.')
1145        #end
1146        #slave_ip.delete
1147        slave_ip._eval_without_enc('exit')
1148      end
1149      begin
1150        top.destroy if top.winfo_exist?
1151      rescue
1152        # ignore
1153      end
1154    }
1155    tag = TkBindTag.new.bind('Destroy', slave_delete_proc)
1156
1157    top.bindtags = top.bindtags.unshift(tag)
1158
1159    # create control frame
1160    TkFrame.new(top, :bg=>'red', :borderwidth=>3, :relief=>'ridge') {|fc|
1161      fc.bindtags = fc.bindtags.unshift(tag)
1162
1163      TkFrame.new(fc, :bd=>0){|f|
1164        TkButton.new(f,
1165                     :text=>'Delete', :bd=>1, :padx=>2, :pady=>0,
1166                     :highlightthickness=>0, :command=>slave_delete_proc
1167                     ).pack(:side=>:right, :fill=>:both)
1168        f.pack(:side=>:right, :fill=>:both, :expand=>true)
1169      }
1170
1171      TkLabel.new(fc, :text=>msg, :padx=>2, :pady=>0,
1172                  :anchor=>:w).pack(:side=>:left, :fill=>:both, :expand=>true)
1173
1174      fc.pack(:side=>:bottom, :fill=>:x)
1175    }
1176
1177    # container frame for slave interpreter
1178    dup_keys['fill'] = :both  unless dup_keys.key?('fill')
1179    dup_keys['expand'] = true unless dup_keys.key?('expand')
1180    c = TkFrame.new(top, :container=>true).pack(dup_keys)
1181    c.bind('Destroy', proc{top.destroy})
1182
1183    # return keys
1184    loadTk_keys['use'] = TkWinfo.id(c)
1185    [loadTk_keys, top.path]
1186  end
1187  private :__create_safetk_frame
1188
1189  def __create_safe_slave_obj(safe_opts, app_name, tk_opts)
1190    raise SecurityError, "no permission to manipulate" unless self.manipulable?
1191
1192    # safe interpreter
1193    ip_name = _create_slave_ip_name
1194    slave_ip = @interp.create_slave(ip_name, true)
1195    slave_ip.instance_eval{
1196      @force_default_encoding ||= TkUtil.untrust([false])
1197      @encoding ||= TkUtil.untrust([nil])
1198      def @encoding.to_s; self.join(nil); end
1199    }
1200    @slave_ip_tbl[ip_name] = slave_ip
1201    def slave_ip.safe_base?
1202      true
1203    end
1204
1205    @interp._eval("::safe::interpInit #{ip_name}")
1206
1207    slave_ip._invoke('set', 'argv0', app_name) if app_name.kind_of?(String)
1208
1209    if tk_opts
1210      tk_opts = __check_safetk_optkeys(tk_opts)
1211      if tk_opts.key?('use')
1212        @slave_ip_top[ip_name] = ''
1213      else
1214        tk_opts, top_path = __create_safetk_frame(slave_ip, ip_name, app_name,
1215                                                  tk_opts)
1216        @slave_ip_top[ip_name] = top_path
1217      end
1218      @interp._eval("::safe::loadTk #{ip_name} #{_keys2opts(tk_opts)}")
1219      @interp._invoke('__replace_slave_tk_commands__', ip_name)
1220    else
1221      @slave_ip_top[ip_name] = nil
1222    end
1223
1224    if safe_opts.key?('deleteHook') || safe_opts.key?(:deleteHook)
1225      @interp._eval("::safe::interpConfigure #{ip_name} " +
1226                    _keys2opts(safe_opts))
1227    else
1228      @interp._eval("::safe::interpConfigure #{ip_name} " +
1229                    _keys2opts(safe_opts) + '-deleteHook {' +
1230                    TkComm._get_eval_string(proc{|slave|
1231                                              self._default_delete_hook(slave)
1232                                            }) + '}')
1233    end
1234
1235    [slave_ip, ip_name]
1236  end
1237
1238  def __create_trusted_slave_obj(name, keys)
1239    raise SecurityError, "no permission to manipulate" unless self.manipulable?
1240
1241    ip_name = _create_slave_ip_name
1242    slave_ip = @interp.create_slave(ip_name, false)
1243    slave_ip.instance_eval{
1244      @force_default_encoding ||= TkUtil.untrust([false])
1245      @encoding ||= TkUtil.untrust([nil])
1246      def @encoding.to_s; self.join(nil); end
1247    }
1248    slave_ip._invoke('set', 'argv0', name) if name.kind_of?(String)
1249    slave_ip._invoke('set', 'argv', _keys2opts(keys))
1250    @interp._invoke('load', '', 'Tk', ip_name)
1251    @interp._invoke('__replace_slave_tk_commands__', ip_name)
1252    @slave_ip_tbl[ip_name] = slave_ip
1253    [slave_ip, ip_name]
1254  end
1255
1256  ######################################
1257
1258  def _create_slave_object(keys={})
1259    raise SecurityError, "no permission to manipulate" unless self.manipulable?
1260
1261    ip = MultiTkIp.new_slave(self, keys={})
1262    @slave_ip_tbl[ip.name] = ip
1263  end
1264
1265  ######################################
1266
1267  def initialize(master, safeip=true, keys={})
1268    if $SAFE >= 4
1269      fail SecurityError, "cannot create a new interpreter at level #{$SAFE}"
1270    end
1271
1272    if safeip == nil && $SAFE >= 2
1273      fail SecurityError, "cannot create a master-ip at level #{$SAFE}"
1274    end
1275
1276    if master.deleted? && safeip == nil
1277      fail RuntimeError, "cannot create a slave of a deleted interpreter"
1278    end
1279
1280    if !master.deleted? && !master.master? && master.safe?
1281      fail SecurityError, "safe-slave-ip cannot create a new interpreter"
1282    end
1283
1284    if safeip == nil && !master.master?
1285      fail SecurityError, "slave-ip cannot create a master-ip"
1286    end
1287
1288    unless keys.kind_of? Hash
1289      fail ArgumentError, "expecting a Hash object for the 2nd argument"
1290    end
1291
1292    @tk_windows = {}
1293    @tk_table_list = []
1294    @slave_ip_tbl = {}
1295    @slave_ip_top = {}
1296    @cb_error_proc = []
1297    @evloop_thread = []
1298
1299    TkUtil.untrust(@tk_windows)    unless @tk_windows.tainted?
1300    TkUtil.untrust(@tk_table_list) unless @tk_table_list.tainted?
1301    TkUtil.untrust(@slave_ip_tbl)  unless @slave_ip_tbl.tainted?
1302    TkUtil.untrust(@slave_ip_top)  unless @slave_ip_top.tainted?
1303    TkUtil.untrust(@cb_error_proc) unless @cb_error_proc.tainted?
1304    TkUtil.untrust(@evloop_thread) unless @evloop_thread.tainted?
1305
1306    @callback_status = []
1307
1308    name, safe, safe_opts, tk_opts = _parse_slaveopts(keys)
1309
1310    safe = 4 if safe && !safe.kind_of?(Fixnum)
1311
1312    @safe_base = false
1313
1314    if safeip == nil
1315      # create master-ip
1316      unless WITH_RUBY_VM
1317        @interp = TclTkIp.new(name, _keys2opts(tk_opts))
1318        @interp.instance_eval{
1319          @force_default_encoding ||= TkUtil.untrust([false])
1320          @encoding ||= TkUtil.untrust([nil])
1321          def @encoding.to_s; self.join(nil); end
1322        }
1323
1324      else ### Ruby 1.9 !!!!!!!!!!!
1325=begin
1326        @interp_thread = Thread.new{
1327          Thread.current[:interp] = interp = TclTkIp.new(name, _keys2opts(tk_opts))
1328          interp.instance_eval{
1329            @force_default_encoding ||= TkUtil.untrust([false])
1330            @encoding ||= TkUtil.untrust([nil])
1331            def @encoding.to_s; self.join(nil); end
1332          }
1333
1334          #sleep
1335          TclTkLib.mainloop(true)
1336        }
1337        until @interp_thread[:interp]
1338          Thread.pass
1339        end
1340        # INTERP_THREAD.run
1341        @interp = @interp_thread[:interp]
1342=end
1343        @interp_thread = Thread.new{
1344          current = Thread.current
1345          begin
1346            current[:interp] = interp = TclTkIp.new(name, _keys2opts(tk_opts))
1347          rescue e
1348            current[:interp] = e
1349            raise e
1350          end
1351          #sleep
1352          #TclTkLib.mainloop(true)
1353          current[:mutex] = mutex = Mutex.new
1354          current[:root_check] = cond_ver = ConditionVariable.new
1355
1356          status = [nil]
1357          def status.value
1358            self[0]
1359          end
1360          def status.value=(val)
1361            self[0] = val
1362          end
1363          current[:status] = status
1364
1365          begin
1366            begin
1367              current[:status].value = interp.mainloop(true)
1368            rescue SystemExit=>e
1369              current[:status].value = e
1370            rescue Exception=>e
1371              current[:status].value = e
1372              retry if interp.has_mainwindow?
1373            ensure
1374              mutex.synchronize{ cond_var.broadcast }
1375            end
1376            current[:status].value = interp.mainloop(false)
1377          ensure
1378            interp.delete
1379          end
1380        }
1381        until @interp_thread[:interp]
1382          Thread.pass
1383        end
1384        # INTERP_THREAD.run
1385        @interp = @interp_thread[:interp]
1386
1387        @evloop_thread[0] = @interp_thread
1388
1389        def self.mainloop(check_root = true)
1390          begin
1391            TclTkLib.set_eventloop_window_mode(true)
1392            @interp_thread.value
1393          ensure
1394            TclTkLib.set_eventloop_window_mode(false)
1395          end
1396        end
1397      end
1398
1399      @interp.instance_eval{
1400        @force_default_encoding ||= TkUtil.untrust([false])
1401        @encoding ||= TkUtil.untrust([nil])
1402        def @encoding.to_s; self.join(nil); end
1403      }
1404
1405      @ip_name = nil
1406
1407      if safe
1408        safe = $SAFE if safe < $SAFE
1409        @safe_level = [safe]
1410      else
1411        @safe_level = [$SAFE]
1412      end
1413
1414    else
1415      # create slave-ip
1416      if safeip || master.safe?
1417        @safe_base = true
1418        @interp, @ip_name = master.__create_safe_slave_obj(safe_opts,
1419                                                           name, tk_opts)
1420        # @interp_thread = nil if RUBY_VERSION < '1.9.0' ### !!!!!!!!!!!
1421        @interp_thread = nil unless WITH_RUBY_VM  ### Ruby 1.9 !!!!!!!!!!!
1422        if safe
1423          safe = master.safe_level if safe < master.safe_level
1424          @safe_level = [safe]
1425        else
1426          @safe_level = [4]
1427        end
1428      else
1429        @interp, @ip_name = master.__create_trusted_slave_obj(name, tk_opts)
1430        # @interp_thread = nil if RUBY_VERSION < '1.9.0' ### !!!!!!!!!!!
1431        @interp_thread = nil unless WITH_RUBY_VM  ### Ruby 1.9 !!!!!!!!!!!
1432        if safe
1433          safe = master.safe_level if safe < master.safe_level
1434          @safe_level = [safe]
1435        else
1436          @safe_level = [master.safe_level]
1437        end
1438      end
1439      @set_alias_proc = proc{|name|
1440        master._invoke('interp', 'alias', @ip_name, name, '', name)
1441      }.freeze
1442    end
1443
1444    @system = Object.new
1445
1446    @wait_on_mainloop = TkUtil.untrust([true, 0])
1447    # @wait_on_mainloop = TkUtil.untrust([false, 0])
1448
1449    @threadgroup  = ThreadGroup.new
1450
1451    @pseudo_toplevel = [false, nil]
1452
1453    @cmd_queue = MultiTkIp::Command_Queue.new(@interp)
1454
1455=begin
1456    @cmd_receiver, @receiver_watchdog = _create_receiver_and_watchdog(@safe_level[0])
1457
1458    @threadgroup.add @cmd_receiver
1459    @threadgroup.add @receiver_watchdog
1460
1461    @threadgroup.enclose
1462=end
1463    @@DEFAULT_MASTER.assign_receiver_and_watchdog(self)
1464
1465    @@IP_TABLE[@threadgroup] = self
1466    @@TK_TABLE_LIST.size.times{
1467      @tk_table_list << TkUtil.untrust({})
1468    }
1469    _init_ip_internal(@@INIT_IP_ENV, @@ADD_TK_PROCS)
1470
1471    class << self
1472      undef :instance_eval
1473    end
1474
1475    # dummy call for initialization
1476    self.eval_proc{ Tk.tk_call('set', 'tcl_patchLevel') }
1477
1478    self.freeze  # defend against modification
1479  end
1480
1481  ######################################
1482
1483  def _default_delete_hook(slave)
1484    raise SecurityError, "no permission to manipulate" unless self.manipulable?
1485    @slave_ip_tbl.delete(slave)
1486    top = @slave_ip_top.delete(slave)
1487    if top.kind_of?(String)
1488      # call default hook of safetk.tcl (ignore exceptions)
1489      if top == ''
1490        begin
1491          @interp._eval("::safe::disallowTk #{slave}")
1492        rescue
1493          warn("Waring: fail to call '::safe::disallowTk'") if $DEBUG
1494        end
1495      else # toplevel path
1496        begin
1497          @interp._eval("::safe::tkDelete {} #{top} #{slave}")
1498        rescue
1499          warn("Waring: fail to call '::safe::tkDelete'") if $DEBUG
1500          begin
1501            @interp._eval("destroy #{top}")
1502          rescue
1503            warn("Waring: fail to destroy toplevel") if $DEBUG
1504          end
1505        end
1506      end
1507    end
1508  end
1509
1510end
1511
1512
1513# get target IP
1514class MultiTkIp
1515  @@CALLBACK_SUBTHREAD = Class.new(Thread){
1516    def self.new(interp, &blk)
1517      super(interp){|ip| Thread.current[:callback_ip] = ip; blk.call}
1518    end
1519
1520    @table = TkUtil.untrust(Hash.new{|h,k| h[k] = TkUtil.untrust([])})
1521    def self.table
1522      @table
1523    end
1524  }
1525
1526  def self._ip_id_
1527    __getip._ip_id_
1528  end
1529  def _ip_id_
1530    # for RemoteTkIp
1531    ''
1532  end
1533
1534  def self.__getip
1535    current = Thread.current
1536    if current.kind_of?(@@CALLBACK_SUBTHREAD)
1537      return current[:callback_ip]
1538    end
1539    if TclTkLib.mainloop_thread? != false && current[:callback_ip]
1540      return current[:callback_ip]
1541    end
1542    if current.group == ThreadGroup::Default
1543      @@DEFAULT_MASTER
1544    else
1545      ip = @@IP_TABLE[current.group]
1546      unless ip
1547        fail SecurityError,
1548          "cannot call Tk methods on #{Thread.current.inspect}"
1549      end
1550      ip
1551    end
1552  end
1553end
1554
1555
1556# aliases of constructor
1557class << MultiTkIp
1558  alias __new new
1559  private :__new
1560
1561  def new_master(safe=nil, keys={}, &blk)
1562    if MultiTkIp::WITH_RUBY_VM
1563      #### TODO !!!!!!
1564      fail RuntimeError,
1565           'sorry, still not support multiple master-interpreters on RubyVM'
1566    end
1567
1568    if safe.kind_of?(Hash)
1569      keys = safe
1570    elsif safe.kind_of?(Integer)
1571      raise ArgumentError, "unexpected argument(s)" unless keys.kind_of?(Hash)
1572      if !keys.key?(:safe) && !keys.key?('safe')
1573        keys[:safe] = safe
1574      end
1575    elsif safe == nil
1576      # do nothing
1577    else
1578      raise ArgumentError, "unexpected argument(s)"
1579    end
1580
1581    ip = __new(__getip, nil, keys)
1582    #ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call) if block_given?
1583    if block_given?
1584      #Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call)}
1585      #Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; yield}.call)}
1586      ip._proc_on_safelevel(&blk).call(ip.safe_level)
1587    end
1588    ip
1589  end
1590
1591  alias new new_master
1592
1593  def new_slave(safe=nil, keys={}, &blk)
1594    if safe.kind_of?(Hash)
1595      keys = safe
1596    elsif safe.kind_of?(Integer)
1597      raise ArgumentError, "unexpected argument(s)" unless keys.kind_of?(Hash)
1598      if !keys.key?(:safe) && !keys.key?('safe')
1599        keys[:safe] = safe
1600      end
1601    elsif safe == nil
1602      # do nothing
1603    else
1604      raise ArgumentError, "unexpected argument(s)"
1605    end
1606
1607    ip = __new(__getip, false, keys)
1608    # ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call) if block_given?
1609    if block_given?
1610      #Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call)}
1611      #Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; yield}.call)}
1612      ip._proc_on_safelevel(&blk).call(ip.safe_level)
1613    end
1614    ip
1615  end
1616  alias new_trusted_slave new_slave
1617
1618  def new_safe_slave(safe=4, keys={}, &blk)
1619    if safe.kind_of?(Hash)
1620      keys = safe
1621    elsif safe.kind_of?(Integer)
1622      raise ArgumentError, "unexpected argument(s)" unless keys.kind_of?(Hash)
1623      if !keys.key?(:safe) && !keys.key?('safe')
1624        keys[:safe] = safe
1625      end
1626    else
1627      raise ArgumentError, "unexpected argument(s)"
1628    end
1629
1630    ip = __new(__getip, true, keys)
1631    # ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call) if block_given?
1632    if block_given?
1633      #Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; Proc.new}.call)}
1634      #Thread.new{ip.eval_proc(proc{$SAFE=ip.safe_level; yield}.call)}
1635      ip._proc_on_safelevel(&blk).call(ip.safe_level)
1636    end
1637    ip
1638  end
1639  alias new_safeTk new_safe_slave
1640end
1641
1642
1643# get info
1644class MultiTkIp
1645  def inspect
1646    s = self.to_s.chop!
1647    if self.manipulable?
1648      if master?
1649        if @interp.deleted?
1650          s << ':deleted-master'
1651        else
1652          s << ':master'
1653        end
1654      else
1655        if @interp.deleted?
1656          s << ':deleted-slave'
1657        elsif @interp.safe?
1658          s << ':safe-slave'
1659        else
1660          s << ':trusted-slave'
1661        end
1662      end
1663    end
1664    s << '>'
1665  end
1666
1667  def master?
1668    if @ip_name
1669      false
1670    else
1671      true
1672    end
1673  end
1674  def self.master?
1675    __getip.master?
1676  end
1677
1678  def slave?
1679    not master?
1680  end
1681  def self.slave?
1682    not self.master?
1683  end
1684
1685  def alive?
1686    raise SecurityError, "no permission to manipulate" unless self.manipulable?
1687    begin
1688      return false unless @cmd_receiver.alive?
1689      return false if @interp.deleted?
1690      return false if @interp._invoke('interp', 'exists', '') == '0'
1691    rescue Exception
1692      return false
1693    end
1694    true
1695  end
1696  def self.alive?
1697    __getip.alive?
1698  end
1699
1700  def path
1701    @ip_name || ''
1702  end
1703  def self.path
1704    __getip.path
1705  end
1706  def ip_name
1707    @ip_name || ''
1708  end
1709  def self.ip_name
1710    __getip.ip_name
1711  end
1712  def to_eval
1713    @ip_name || ''
1714  end
1715  def self.to_eval
1716    __getip.to_eval
1717  end
1718
1719  def slaves(all = false)
1720    raise SecurityError, "no permission to manipulate" unless self.manipulable?
1721    @interp._invoke('interp','slaves').split.map!{|name|
1722      if @slave_ip_tbl.key?(name)
1723        @slave_ip_tbl[name]
1724      elsif all
1725        name
1726      else
1727        nil
1728      end
1729    }.compact!
1730  end
1731  def self.slaves(all = false)
1732    __getip.slaves(all)
1733  end
1734
1735  def manipulable?
1736    return true if (Thread.current.group == ThreadGroup::Default)
1737    ip = MultiTkIp.__getip
1738    (ip == self) || ip._is_master_of?(@interp)
1739  end
1740  def self.manipulable?
1741    true
1742  end
1743
1744  def _is_master_of?(tcltkip_obj)
1745    tcltkip_obj.slave_of?(@interp)
1746  end
1747  protected :_is_master_of?
1748end
1749
1750
1751# instance methods to treat tables
1752class MultiTkIp
1753  def _tk_cmd_tbl
1754    tbl = {}
1755    MultiTkIp.tk_cmd_tbl.each{|id, ent| tbl[id] = ent if ent.ip == self }
1756    tbl
1757  end
1758
1759  def _tk_windows
1760    @tk_windows
1761  end
1762
1763  def _tk_table_list
1764    @tk_table_list
1765  end
1766
1767  def _add_new_tables
1768    (@@TK_TABLE_LIST.size - @tk_table_list.size).times{
1769      @tk_table_list << TkUtil.untrust({})
1770    }
1771  end
1772
1773  def _init_ip_env(script)
1774    self.eval_proc{script.call(self)}
1775  end
1776
1777  def _add_tk_procs(name, args, body)
1778    return if slave?
1779    @interp._invoke('proc', name, args, body) if args && body
1780    @interp._invoke('interp', 'slaves').split.each{|slave|
1781      @interp._invoke('interp', 'alias', slave, name, '', name)
1782    }
1783  end
1784
1785  def _remove_tk_procs(*names)
1786    return if slave?
1787    names.each{|name|
1788      name = name.to_s
1789
1790      return if @interp.deleted?
1791      @interp._invoke('rename', name, '')
1792
1793      return if @interp.deleted?
1794      @interp._invoke('interp', 'slaves').split.each{|slave|
1795        return if @interp.deleted?
1796        @interp._invoke('interp', 'alias', slave, name, '') rescue nil
1797      }
1798    }
1799  end
1800
1801  def _init_ip_internal(init_ip_env, add_tk_procs)
1802    #init_ip_env.each{|script| self.eval_proc{script.call(self)}}
1803    init_ip_env.each{|script| self._init_ip_env(script)}
1804    add_tk_procs.each{|name, args, body|
1805      if master?
1806        @interp._invoke('proc', name, args, body) if args && body
1807      else
1808        @set_alias_proc.call(name)
1809      end
1810    }
1811  end
1812end
1813
1814
1815# class methods to treat tables
1816class MultiTkIp
1817  def self.tk_cmd_tbl
1818    @@TK_CMD_TBL
1819  end
1820  def self.tk_windows
1821    __getip._tk_windows
1822  end
1823  def self.tk_object_table(id)
1824    __getip._tk_table_list[id]
1825  end
1826  def self.create_table
1827    if __getip.slave?
1828      begin
1829        raise SecurityError, "slave-IP has no permission creating a new table"
1830      rescue SecurityError => e
1831        #p e.backtrace
1832        # Is called on a Ruby/Tk library?
1833        caller_info = e.backtrace[1]
1834        if caller_info =~ %r{^#{MultiTkIp::BASE_DIR}/(tk|tkextlib)/[^:]+\.rb:}
1835          # Probably, caller is a Ruby/Tk library  -->  allow creating
1836        else
1837          raise e
1838        end
1839      end
1840    end
1841
1842    id = @@TK_TABLE_LIST.size
1843    obj = Object.new
1844    @@TK_TABLE_LIST << obj
1845    obj.instance_variable_set(:@id, id)
1846    obj.instance_variable_set(:@mutex, Mutex.new)
1847    obj.instance_eval{
1848      def self.mutex
1849        @mutex
1850      end
1851      def self.method_missing(m, *args)
1852        MultiTkIp.tk_object_table(@id).__send__(m, *args)
1853      end
1854    }
1855    obj.freeze
1856    @@IP_TABLE.each{|tg, ip| ip._add_new_tables }
1857    return obj
1858  end
1859
1860  def self.init_ip_env(script = Proc.new)
1861    @@INIT_IP_ENV << script
1862    if __getip.slave?
1863      begin
1864        raise SecurityError, "slave-IP has no permission initializing IP env"
1865      rescue SecurityError => e
1866        #p e.backtrace
1867        # Is called on a Ruby/Tk library?
1868        caller_info = e.backtrace[1]
1869        if caller_info =~ %r{^#{MultiTkIp::BASE_DIR}/(tk|tkextlib)/[^:]+\.rb:}
1870          # Probably, caller is a Ruby/Tk library  -->  allow creating
1871        else
1872          raise e
1873        end
1874      end
1875    end
1876
1877    # @@IP_TABLE.each{|tg, ip|
1878    #   ip._init_ip_env(script)
1879    # }
1880    @@DEFAULT_MASTER.__init_ip_env__(@@IP_TABLE, script)
1881  end
1882
1883  def self.add_tk_procs(name, args=nil, body=nil)
1884    if name.kind_of?(Array) # => an array of [name, args, body]
1885      name.each{|param| self.add_tk_procs(*param)}
1886    else
1887      name = name.to_s
1888      @@ADD_TK_PROCS << [name, args, body]
1889      @@IP_TABLE.each{|tg, ip|
1890        ip._add_tk_procs(name, args, body)
1891      }
1892    end
1893  end
1894
1895  def self.remove_tk_procs(*names)
1896    names.each{|name|
1897      name = name.to_s
1898      @@ADD_TK_PROCS.delete_if{|elem|
1899        elem.kind_of?(Array) && elem[0].to_s == name
1900      }
1901    }
1902    @@IP_TABLE.each{|tg, ip|
1903      ip._remove_tk_procs(*names)
1904    }
1905  end
1906
1907  def self.init_ip_internal
1908    __getip._init_ip_internal(@@INIT_IP_ENV, @@ADD_TK_PROCS)
1909  end
1910end
1911
1912# for callback operation
1913class MultiTkIp
1914  def self.cb_entry_class
1915    @@CB_ENTRY_CLASS
1916  end
1917  def self.get_cb_entry(cmd)
1918    @@CB_ENTRY_CLASS.new(__getip, cmd).freeze
1919  end
1920
1921=begin
1922  def cb_eval(cmd, *args)
1923    #self.eval_callback{ TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *args)) }
1924    #ret = self.eval_callback{ TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *args)) }
1925    ret = self.eval_callback(*args){|safe, *params|
1926      $SAFE=safe if $SAFE < safe
1927      TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *params))
1928    }
1929    if ret.kind_of?(Exception)
1930      raise ret
1931    end
1932    ret
1933  end
1934=end
1935  def cb_eval(cmd, *args)
1936    self.eval_callback(*args,
1937                       &_proc_on_safelevel{|*params|
1938                         TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *params))
1939                       })
1940  end
1941=begin
1942  def cb_eval(cmd, *args)
1943    self.eval_callback(*args){|safe, *params|
1944      $SAFE=safe if $SAFE < safe
1945      # TkUtil.eval_cmd(cmd, *params)
1946      TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *params))
1947    }
1948  end
1949=end
1950=begin
1951  def cb_eval(cmd, *args)
1952    @callback_status[0] ||= TkVariable.new
1953    @callback_status[1] ||= TkVariable.new
1954    st, val = @callback_status
1955    th = Thread.new{
1956      self.eval_callback(*args){|safe, *params|
1957        #p [status, val, safe, *params]
1958        $SAFE=safe if $SAFE < safe
1959        begin
1960          TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *params))
1961        rescue TkCallbackContinue
1962          st.value = 4
1963        rescue TkCallbackBreak
1964          st.value = 3
1965        rescue TkCallbackReturn
1966          st.value = 2
1967        rescue Exception => e
1968          val.value = e.message
1969          st.value = 1
1970        else
1971          st.value = 0
1972        end
1973      }
1974    }
1975    begin
1976      st.wait
1977      status = st.numeric
1978      retval = val.value
1979    rescue => e
1980      fail e
1981    end
1982
1983    if status == 1
1984      fail RuntimeError, retval
1985    elsif status == 2
1986      fail TkCallbackReturn, "Tk callback returns 'return' status"
1987    elsif status == 3
1988      fail TkCallbackBreak, "Tk callback returns 'break' status"
1989    elsif status == 4
1990      fail TkCallbackContinue, "Tk callback returns 'continue' status"
1991    else
1992      ''
1993    end
1994  end
1995=end
1996
1997end
1998
1999# pseudo-toplevel operation support
2000class MultiTkIp
2001  # instance method
2002  def __pseudo_toplevel
2003    ip = MultiTkIp.__getip
2004    (ip == @@DEFAULT_MASTER || ip == self) &&
2005      self.__pseudo_toplevel_evaluable? && @pseudo_toplevel[1]
2006  end
2007
2008  def __pseudo_toplevel=(m)
2009    unless (Thread.current.group == ThreadGroup::Default &&
2010              MultiTkIp.__getip == @@DEFAULT_MASTER)
2011      fail SecurityError, "no permission to manipulate"
2012    end
2013
2014    # if m.kind_of?(Module) && m.respond_to?(:pseudo_toplevel_evaluable?)
2015    if m.respond_to?(:pseudo_toplevel_evaluable?)
2016      @pseudo_toplevel[0] = true
2017      @pseudo_toplevel[1] = m
2018    else
2019      fail ArgumentError, 'fail to set pseudo-toplevel'
2020    end
2021    self
2022  end
2023
2024  def __pseudo_toplevel_evaluable?
2025    begin
2026      @pseudo_toplevel[0] && @pseudo_toplevel[1].pseudo_toplevel_evaluable?
2027    rescue Exception
2028      false
2029    end
2030  end
2031
2032  def __pseudo_toplevel_evaluable=(mode)
2033    unless (Thread.current.group == ThreadGroup::Default &&
2034              MultiTkIp.__getip == @@DEFAULT_MASTER)
2035      fail SecurityError, "no permission to manipulate"
2036    end
2037
2038    @pseudo_toplevel[0] = (mode)? true: false
2039  end
2040end
2041
2042
2043################################################
2044# use pseudo-toplevel feature of MultiTkIp ?
2045if (!defined?(Use_PseudoToplevel_Feature_of_MultiTkIp) ||
2046      Use_PseudoToplevel_Feature_of_MultiTkIp)
2047  module MultiTkIp_PseudoToplevel_Evaluable
2048    #def pseudo_toplevel_eval(body = Proc.new)
2049    #  Thread.current[:TOPLEVEL] = self
2050    #  begin
2051    #    body.call
2052    #  ensure
2053    #    Thread.current[:TOPLEVEL] = nil
2054    #  end
2055    #end
2056
2057    def pseudo_toplevel_evaluable?
2058      @pseudo_toplevel_evaluable
2059    end
2060
2061    def pseudo_toplevel_evaluable=(mode)
2062      @pseudo_toplevel_evaluable = (mode)? true: false
2063    end
2064
2065    def self.extended(mod)
2066      mod.__send__(:extend_object, mod)
2067      mod.instance_variable_set('@pseudo_toplevel_evaluable', true)
2068    end
2069  end
2070
2071  class Object
2072    alias __method_missing_alias_for_MultiTkIp__ method_missing
2073    private :__method_missing_alias_for_MultiTkIp__
2074
2075    def method_missing(id, *args)
2076      begin
2077        has_top = (top = MultiTkIp.__getip.__pseudo_toplevel) &&
2078          top.respond_to?(:pseudo_toplevel_evaluable?) &&
2079          top.pseudo_toplevel_evaluable? &&
2080          top.respond_to?(id)
2081      rescue Exception => e
2082        has_top = false
2083      end
2084
2085      if has_top
2086        top.__send__(id, *args)
2087      else
2088        __method_missing_alias_for_MultiTkIp__(id, *args)
2089      end
2090    end
2091  end
2092else
2093  # dummy
2094  module MultiTkIp_PseudoToplevel_Evaluable
2095    def pseudo_toplevel_evaluable?
2096      false
2097    end
2098  end
2099end
2100
2101
2102################################################
2103# evaluate a procedure on the proper interpreter
2104class MultiTkIp
2105  # instance & class method
2106  def _proc_on_safelevel(cmd=nil) # require a block for eval
2107    if cmd
2108      if cmd.kind_of?(Method)
2109        _proc_on_safelevel{|*args| cmd.call(*args)}
2110      else
2111        _proc_on_safelevel(&cmd)
2112      end
2113    else
2114      #Proc.new{|safe, *args| $SAFE=safe if $SAFE < safe; yield(*args)}
2115      Proc.new{|safe, *args|
2116        # avoid security error on Exception objects
2117        untrust_proc = proc{|err|
2118          begin
2119            err.untrust if err.respond_to?(:untrust)
2120          rescue SecurityError
2121          end
2122          err
2123        }
2124        $SAFE=safe if $SAFE < safe;
2125        begin
2126          yield(*args)
2127        rescue Exception => e
2128          fail untrust_proc.call(e)
2129        end
2130      }
2131    end
2132  end
2133  def MultiTkIp._proc_on_safelevel(cmd=nil, &blk)
2134    MultiTkIp.__getip._proc_on_safelevel(cmd, &blk)
2135  end
2136
2137  def _proc_on_current_safelevel(cmd=nil, &blk) # require a block for eval
2138    safe = $SAFE
2139    cmd = _proc_on_safelevel(cmd, &blk)
2140    Proc.new{|*args| cmd.call(safe, *args)}
2141  end
2142  def MultiTkIp._proc_on_current_safelevel(cmd=nil, &blk)
2143    MultiTkIp.__getip._proc_on_current_safelevel(cmd, &blk)
2144  end
2145
2146  ######################################
2147  # instance method
2148  def eval_proc_core(req_val, cmd, *args)
2149    # check
2150    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2151    unless cmd.kind_of?(Proc) || cmd.kind_of?(Method)
2152      raise RuntimeError, "A Proc/Method object is expected for the 'cmd' argument"
2153    end
2154
2155    # on IP thread
2156    if @cmd_receiver == Thread.current ||
2157        (!req_val && TclTkLib.mainloop_thread? != false) # callback
2158      begin
2159        ret = cmd.call(safe_level, *args)
2160      rescue SystemExit => e
2161        # exit IP
2162        warn("Warning: "+ e.inspect + " on " + self.inspect) if $DEBUG
2163        begin
2164          self._eval_without_enc('exit')
2165        rescue Exception => e
2166        end
2167        self.delete
2168        ret = nil
2169      rescue Exception => e
2170        if $DEBUG
2171          warn("Warning: " + e.class.inspect +
2172               ((e.message.length > 0)? ' "' + e.message + '"': '') +
2173               " on " + self.inspect)
2174        end
2175=begin
2176        begin
2177          bt = _toUTF8(e.backtrace.join("\n"))
2178          bt.instance_variable_set(:@encoding, 'utf-8')
2179        rescue Exception
2180          bt = e.backtrace.join("\n")
2181        end
2182        begin
2183          @interp._set_global_var('errorInfo', bt)
2184        rescue Exception
2185        end
2186=end
2187        ret = e
2188      end
2189      return ret
2190    end
2191
2192    # send cmd to the proc-queue
2193    unless req_val
2194      begin
2195        @cmd_queue.enq([nil, cmd, *args])
2196      rescue Exception => e
2197        # ignore
2198        if $DEBUG
2199          warn("Warning: " + e.class.inspect +
2200               ((e.message.length > 0)? ' "' + e.message + '"': '') +
2201               " on " + self.inspect)
2202        end
2203        return e
2204      end
2205      return nil
2206    end
2207
2208    # send and get return value by exception
2209    begin
2210      @cmd_queue.enq([Thread.current, cmd, *args])
2211      Thread.stop
2212    rescue MultiTkIp_OK => ret
2213      # return value
2214      return ret.value
2215    rescue SystemExit => e
2216      # exit IP
2217      warn("Warning: " + e.inspect + " on " + self.inspect) if $DEBUG
2218      begin
2219        self._eval_without_enc('exit')
2220      rescue Exception
2221      end
2222      if !self.deleted? && !safe? && allow_ruby_exit?
2223        self.delete
2224        fail e
2225      else
2226        self.delete
2227      end
2228    rescue Exception => e
2229      if $DEBUG
2230        warn("Warning: " + e.class.inspect +
2231             ((e.message.length > 0)? ' "' + e.message + '"': '') +
2232             " on " + self.inspect)
2233      end
2234      return e
2235    end
2236    return nil
2237  end
2238  private :eval_proc_core
2239
2240if false && WITH_RUBY_VM  ### Ruby 1.9
2241  # Not stable, so disable this feature
2242  def eval_callback(*args)
2243    if block_given?
2244      cmd = Proc.new
2245    else
2246      cmd = args.shift
2247    end
2248    begin
2249      if @@CALLBACK_SUBTHREAD.table[self].index(Thread.current)
2250        last_th = nil
2251      else
2252        last_th = @@CALLBACK_SUBTHREAD.table[self][-1]
2253      end
2254      @@CALLBACK_SUBTHREAD.new(self){
2255        @@CALLBACK_SUBTHREAD.table[self] << Thread.current
2256        begin
2257          last_th.join if last_th
2258          eval_proc_core(false, cmd, *args)
2259        rescue Exception=>e
2260          e
2261        ensure
2262          @@CALLBACK_SUBTHREAD.table[self].delete(Thread.current)
2263        end
2264      }
2265    end
2266  end
2267else  ### Ruby 1.8
2268  def eval_callback(*args)
2269    if block_given?
2270      cmd = Proc.new
2271    else
2272      cmd = args.shift
2273    end
2274    begin
2275      eval_proc_core(false, cmd, *args)
2276    rescue Exception=>e
2277      e
2278    ensure
2279    end
2280  end
2281end
2282
2283  def eval_proc(*args, &blk)
2284    if block_given?
2285      cmd = _proc_on_safelevel(&blk)
2286    else
2287      unless (cmd = args.shift)
2288        fail ArgumentError, "A Proc or Method object is expected for 1st argument"
2289      end
2290      cmd = _proc_on_safelevel(&cmd)
2291    end
2292    if TclTkLib.mainloop_thread? == true
2293      # call from eventloop
2294      current = Thread.current
2295      backup_ip = current[:callback_ip]
2296      current[:callback_ip] = self
2297      begin
2298        eval_proc_core(false, cmd, *args)
2299      ensure
2300        current[:callback_ip] = backup_ip
2301      end
2302    else
2303      eval_proc_core(true,
2304                     proc{|safe, *params|
2305                       Thread.new{cmd.call(safe, *params)}.value
2306                     },
2307                     *args)
2308    end
2309  end
2310=begin
2311  def eval_proc(*args)
2312    # The scope of the eval-block of 'eval_proc' method is different from
2313    # the external. If you want to pass local values to the eval-block,
2314    # use arguments of eval_proc method. They are passed to block-arguments.
2315    if block_given?
2316      cmd = Proc.new
2317    else
2318      unless (cmd = args.shift)
2319        fail ArgumentError, "A Proc or Method object is expected for 1st argument"
2320      end
2321    end
2322    if TclTkLib.mainloop_thread? == true
2323      # call from eventloop
2324      current = Thread.current
2325      backup_ip = current[:callback_ip]
2326      current[:callback_ip] = self
2327      begin
2328        eval_proc_core(false,
2329	               proc{|safe, *params|
2330		         $SAFE=safe if $SAFE < safe
2331                         cmd.call(*params)
2332                       }, *args)
2333      ensure
2334        current[:callback_ip] = backup_ip
2335      end
2336    else
2337      eval_proc_core(true,
2338                     proc{|safe, *params|
2339                       $SAFE=safe if $SAFE < safe
2340                       Thread.new(*params, &cmd).value
2341                     },
2342                     *args)
2343    end
2344  end
2345=end
2346  alias call eval_proc
2347
2348  def bg_eval_proc(*args)
2349    if block_given?
2350      cmd = Proc.new
2351    else
2352      unless (cmd = args.shift)
2353        fail ArgumentError, "A Proc or Method object is expected for 1st argument"
2354      end
2355    end
2356    Thread.new{
2357      eval_proc(cmd, *args)
2358=begin
2359      eval_proc_core(false,
2360                     proc{|safe, *params|
2361                       $SAFE=safe if $SAFE < safe
2362                       Thread.new(*params, &cmd).value
2363                     },
2364                     safe_level, *args)
2365=end
2366    }
2367  end
2368  alias background_eval_proc bg_eval_proc
2369  alias thread_eval_proc bg_eval_proc
2370  alias bg_call bg_eval_proc
2371  alias background_call bg_eval_proc
2372
2373  def eval_string(cmd, *eval_args)
2374    # cmd string ==> proc
2375    unless cmd.kind_of?(String)
2376      raise RuntimeError, "A String object is expected for the 'cmd' argument"
2377    end
2378
2379    eval_proc_core(true,
2380                   proc{|safe|
2381                     Kernel.eval("$SAFE=#{safe} if $SAFE < #{safe};" << cmd,
2382                                 *eval_args)
2383                   })
2384  end
2385  alias eval_str eval_string
2386
2387  def bg_eval_string(cmd, *eval_args)
2388    # cmd string ==> proc
2389    unless cmd.kind_of?(String)
2390      raise RuntimeError, "A String object is expected for the 'cmd' argument"
2391    end
2392    Thread.new{
2393      eval_proc_core(true,
2394                     proc{|safe|
2395                       Kernel.eval("$SAFE=#{safe} if $SAFE < #{safe};" << cmd,
2396                                   *eval_args)
2397                     })
2398    }
2399  end
2400  alias background_eval_string bg_eval_string
2401  alias bg_eval_str bg_eval_string
2402  alias background_eval_str bg_eval_string
2403
2404  def eval(*args, &blk)
2405    if block_given?
2406      eval_proc(*args, &blk)
2407    elsif args[0]
2408      if args[0].respond_to?(:call)
2409        eval_proc(*args)
2410      else
2411        eval_string(*args)
2412      end
2413    else
2414      fail ArgumentError, "no argument to eval"
2415    end
2416  end
2417
2418  def bg_eval(*args, &blk)
2419    if block_given?
2420      bg_eval_proc(*args, &blk)
2421    elsif args[0]
2422      if args[0].respond_to?(:call)
2423        bg_eval_proc(*args)
2424      else
2425        bg_eval_string(*args)
2426      end
2427    else
2428      fail ArgumentError, "no argument to eval"
2429    end
2430  end
2431  alias background_eval bg_eval
2432end
2433
2434class << MultiTkIp
2435  # class method
2436  def eval_proc(*args, &blk)
2437    # class ==> interp object
2438    __getip.eval_proc(*args, &blk)
2439  end
2440  alias call eval_proc
2441
2442  def bg_eval_proc(*args, &blk)
2443    # class ==> interp object
2444    __getip.bg_eval_proc(*args, &blk)
2445  end
2446  alias background_eval_proc bg_eval_proc
2447  alias thread_eval_proc bg_eval_proc
2448  alias bg_call bg_eval_proc
2449  alias background_call bg_eval_proc
2450
2451  def eval_string(cmd, *eval_args)
2452    # class ==> interp object
2453    __getip.eval_string(cmd, *eval_args)
2454  end
2455  alias eval_str eval_string
2456
2457  def bg_eval_string(cmd, *eval_args)
2458    # class ==> interp object
2459    __getip.bg_eval_string(cmd, *eval_args)
2460  end
2461  alias background_eval_string bg_eval_string
2462  alias bg_eval_str bg_eval_string
2463  alias background_eval_str bg_eval_string
2464
2465  def eval(*args, &blk)
2466    # class ==> interp object
2467    __getip.eval(*args, &blk)
2468  end
2469  def bg_eval(*args, &blk)
2470    # class ==> interp object
2471    __getip.bg_eval(*args, &blk)
2472  end
2473  alias background_eval bg_eval
2474end
2475
2476
2477# event loop
2478# all master/slave IPs are controled by only one event-loop
2479class MultiTkIp
2480  def self.default_master?
2481    __getip == @@DEFAULT_MASTER
2482  end
2483end
2484class << MultiTkIp
2485  def mainloop(check_root = true)
2486    __getip.mainloop(check_root)
2487  end
2488  def mainloop_watchdog(check_root = true)
2489    __getip.mainloop_watchdog(check_root)
2490  end
2491  def do_one_event(flag = TclTkLib::EventFlag::ALL)
2492    __getip.do_one_event(flag)
2493  end
2494  def mainloop_abort_on_exception
2495    # __getip.mainloop_abort_on_exception
2496    TclTkLib.mainloop_abort_on_exception
2497  end
2498  def mainloop_abort_on_exception=(mode)
2499    # __getip.mainloop_abort_on_exception=(mode)
2500    TclTkLib.mainloop_abort_on_exception=(mode)
2501  end
2502  def set_eventloop_tick(tick)
2503    __getip.set_eventloop_tick(tick)
2504  end
2505  def get_eventloop_tick
2506    __getip.get_eventloop_tick
2507  end
2508  def set_no_event_wait(tick)
2509    __getip.set_no_event_wait(tick)
2510  end
2511  def get_no_event_wait
2512    __getip.get_no_event_wait
2513  end
2514  def set_eventloop_weight(loop_max, no_event_tick)
2515    __getip.set_eventloop_weight(loop_max, no_event_tick)
2516  end
2517  def get_eventloop_weight
2518    __getip.get_eventloop_weight
2519  end
2520end
2521
2522# class methods to delegate to TclTkIp
2523class << MultiTkIp
2524  def method_missing(id, *args)
2525    __getip.__send__(id, *args)
2526  end
2527
2528  def make_safe
2529    __getip.make_safe
2530  end
2531
2532  def safe?
2533    __getip.safe?
2534  end
2535
2536  def safe_base?
2537    begin
2538      __getip.safe_base?
2539    rescue
2540      false
2541    end
2542  end
2543
2544  def allow_ruby_exit?
2545    __getip.allow_ruby_exit?
2546  end
2547
2548  def allow_ruby_exit= (mode)
2549    __getip.allow_ruby_exit = mode
2550  end
2551
2552  def delete
2553    __getip.delete
2554  end
2555
2556  def deleted?
2557    __getip.deleted?
2558  end
2559
2560  def has_mainwindow?
2561    __getip.has_mainwindow?
2562  end
2563
2564  def invalid_namespace?
2565    __getip.invalid_namespace?
2566  end
2567
2568  def abort(msg = nil)
2569    __getip.abort(msg)
2570  end
2571
2572  def exit(st = true)
2573    __getip.exit(st)
2574  end
2575
2576  def exit!(st = false)
2577    __getip.exit!(st)
2578  end
2579
2580  def restart(app_name = nil, keys = {})
2581    init_ip_internal
2582
2583    __getip._invoke('set', 'argv0', app_name) if app_name
2584    if keys.kind_of?(Hash)
2585      __getip._invoke('set', 'argv', _keys2opts(keys))
2586    end
2587
2588    __getip.restart
2589  end
2590
2591  def _eval(str)
2592    __getip._eval(str)
2593  end
2594
2595  def _invoke(*args)
2596    __getip._invoke(*args)
2597  end
2598
2599  def _eval_without_enc(str)
2600    __getip._eval_without_enc(str)
2601  end
2602
2603  def _invoke_without_enc(*args)
2604    __getip._invoke_without_enc(*args)
2605  end
2606
2607  def _eval_with_enc(str)
2608    __getip._eval_with_enc(str)
2609  end
2610
2611  def _invoke_with_enc(*args)
2612    __getip._invoke_with_enc(*args)
2613  end
2614
2615  def _toUTF8(str, encoding=nil)
2616    __getip._toUTF8(str, encoding)
2617  end
2618
2619  def _fromUTF8(str, encoding=nil)
2620    __getip._fromUTF8(str, encoding)
2621  end
2622
2623  def _thread_vwait(var)
2624    __getip._thread_vwait(var)
2625  end
2626
2627  def _thread_tkwait(mode, target)
2628    __getip._thread_tkwait(mode, target)
2629  end
2630
2631  def _return_value
2632    __getip._return_value
2633  end
2634
2635  def _get_variable(var, flag)
2636    __getip._get_variable(var, flag)
2637  end
2638  def _get_variable2(var, idx, flag)
2639    __getip._get_variable2(var, idx, flag)
2640  end
2641  def _set_variable(var, value, flag)
2642    __getip._set_variable(var, value, flag)
2643  end
2644  def _set_variable2(var, idx, value, flag)
2645    __getip._set_variable2(var, idx, value, flag)
2646  end
2647  def _unset_variable(var, flag)
2648    __getip._unset_variable(var, flag)
2649  end
2650  def _unset_variable2(var, idx, flag)
2651    __getip._unset_variable2(var, idx, flag)
2652  end
2653
2654  def _get_global_var(var)
2655    __getip._get_global_var(var)
2656  end
2657  def _get_global_var2(var, idx)
2658    __getip._get_global_var2(var, idx)
2659  end
2660  def _set_global_var(var, value)
2661    __getip._set_global_var(var, value)
2662  end
2663  def _set_global_var2(var, idx, value)
2664    __getip._set_global_var2(var, idx, value)
2665  end
2666  def _unset_global_var(var)
2667    __getip._unset_global_var(var)
2668  end
2669  def _unset_global_var2(var, idx)
2670    __getip._unset_global_var2(var, idx)
2671  end
2672
2673  def _make_menu_embeddable(menu_path)
2674    __getip._make_menu_embeddable(menu_path)
2675  end
2676
2677  def _split_tklist(str)
2678    __getip._split_tklist(str)
2679  end
2680  def _merge_tklist(*args)
2681    __getip._merge_tklist(*args)
2682  end
2683  def _conv_listelement(arg)
2684    __getip._conv_listelement(arg)
2685  end
2686
2687  def _create_console
2688    __getip._create_console
2689  end
2690end
2691
2692
2693# wrap methods on TclTkLib : not permit calling TclTkLib module methods
2694class << TclTkLib
2695  def mainloop(check_root = true)
2696    MultiTkIp.mainloop(check_root)
2697  end
2698  def mainloop_watchdog(check_root = true)
2699    MultiTkIp.mainloop_watchdog(check_root)
2700  end
2701  def do_one_event(flag = TclTkLib::EventFlag::ALL)
2702    MultiTkIp.do_one_event(flag)
2703  end
2704  #def mainloop_abort_on_exception
2705  #  MultiTkIp.mainloop_abort_on_exception
2706  #end
2707  #def mainloop_abort_on_exception=(mode)
2708  #  MultiTkIp.mainloop_abort_on_exception=(mode)
2709  #end
2710  def set_eventloop_tick(tick)
2711    MultiTkIp.set_eventloop_tick(tick)
2712  end
2713  def get_eventloop_tick
2714    MultiTkIp.get_eventloop_tick
2715  end
2716  def set_no_event_wait(tick)
2717    MultiTkIp.set_no_event_wait(tick)
2718  end
2719  def get_no_event_wait
2720    MultiTkIp.get_no_event_wait
2721  end
2722  def set_eventloop_weight(loop_max, no_event_tick)
2723    MultiTkIp.set_eventloop_weight(loop_max, no_event_tick)
2724  end
2725  def get_eventloop_weight
2726    MultiTkIp.get_eventloop_weight
2727  end
2728  def restart(*args)
2729    MultiTkIp.restart(*args)
2730  end
2731
2732  def _merge_tklist(*args)
2733    MultiTkIp._merge_tklist(*args)
2734  end
2735  def _conv_listelement(arg)
2736    MultiTkIp._conv_listelement(arg)
2737  end
2738end
2739
2740
2741# depend on TclTkIp
2742class MultiTkIp
2743#  def mainloop(check_root = true, restart_on_dead = true)
2744  def mainloop(check_root = true, restart_on_dead = false)
2745    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2746
2747    if WITH_RUBY_VM  ### Ruby 1.9 !!!!!!!!!!!
2748      return @interp_thread.value if @interp_thread
2749    end
2750
2751    #return self if self.slave?
2752    #return self if self != @@DEFAULT_MASTER
2753    if self != @@DEFAULT_MASTER
2754      if @wait_on_mainloop[0]
2755        begin
2756          @wait_on_mainloop[1] += 1
2757          if $SAFE >= 4
2758	    _receiver_mainloop(check_root).join
2759          else
2760            @cmd_queue.enq([@system, 'call_mainloop',
2761                            Thread.current, check_root])
2762            Thread.stop
2763          end
2764        rescue MultiTkIp_OK => ret
2765          # return value
2766          if ret.value.kind_of?(Thread)
2767            return ret.value.value
2768          else
2769            return ret.value
2770          end
2771        rescue SystemExit => e
2772          # exit IP
2773          warn("Warning: " + e.inspect + " on " + self.inspect) if $DEBUG
2774          begin
2775            self._eval_without_enc('exit')
2776          rescue Exception
2777          end
2778          self.delete
2779        rescue StandardError => e
2780          if $DEBUG
2781            warn("Warning: " + e.class.inspect +
2782                 ((e.message.length > 0)? ' "' + e.message + '"': '') +
2783                 " on " + self.inspect)
2784          end
2785          return e
2786        rescue Exception => e
2787	  return e
2788        ensure
2789          @wait_on_mainloop[1] -= 1
2790        end
2791      end
2792      return
2793    end
2794
2795    unless restart_on_dead
2796      @wait_on_mainloop[1] += 1
2797=begin
2798      begin
2799        @interp.mainloop(check_root)
2800      rescue StandardError => e
2801        if $DEBUG
2802          warn("Warning: " + e.class.inspect +
2803               ((e.message.length > 0)? ' "' + e.message + '"': '') +
2804               " on " + self.inspect)
2805        end
2806      end
2807=end
2808      begin
2809	@interp.mainloop(check_root)
2810      ensure
2811	@wait_on_mainloop[1] -= 1
2812      end
2813    else
2814      loop do
2815        break unless self.alive?
2816        if check_root
2817          begin
2818            break if TclTkLib.num_of_mainwindows == 0
2819          rescue StandardError
2820            break
2821          end
2822        end
2823        break if @interp.deleted?
2824        begin
2825	  @wait_on_mainloop[1] += 1
2826          @interp.mainloop(check_root)
2827        rescue StandardError => e
2828          if TclTkLib.mainloop_abort_on_exception != nil
2829            #STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect,
2830            #             " exception (ignore) : ", $!.message, "\n");
2831            if $DEBUG
2832              warn("Warning: Tk mainloop receives " << e.class.inspect <<
2833                   " exception (ignore) : " << e.message);
2834            end
2835          end
2836          #raise e
2837        rescue Exception => e
2838=begin
2839          if TclTkLib.mainloop_abort_on_exception != nil
2840            #STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect,
2841            #             " exception (ignore) : ", $!.message, "\n");
2842            if $DEBUG
2843              warn("Warning: Tk mainloop receives " << e.class.inspect <<
2844                   " exception (ignore) : " << e.message);
2845            end
2846          end
2847=end
2848          raise e
2849        ensure
2850          @wait_on_mainloop[1] -= 1
2851          Thread.pass  # avoid eventloop conflict
2852        end
2853      end
2854    end
2855    self
2856  end
2857
2858  def make_safe
2859    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2860    @interp.make_safe
2861  end
2862
2863  def safe?
2864    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2865    @interp.safe?
2866  end
2867
2868  def safe_base?
2869    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2870    @safe_base
2871  end
2872
2873  def allow_ruby_exit?
2874    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2875    @interp.allow_ruby_exit?
2876  end
2877
2878  def allow_ruby_exit= (mode)
2879    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2880    @interp.allow_ruby_exit = mode
2881  end
2882
2883  def delete
2884    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2885    @slave_ip_tbl.each{|name, subip|
2886      _destroy_slaves_of_slaveIP(subip)
2887=begin
2888      begin
2889        subip._invoke('destroy', '.') unless subip.deleted?
2890      rescue Exception
2891      end
2892=end
2893      begin
2894        # subip._eval_without_enc("foreach i [after info] {after cancel $i}")
2895	unless subip.deleted?
2896	  after_ids = subip._eval_without_enc("after info")
2897	  subip._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
2898	end
2899      rescue Exception
2900      end
2901
2902      # safe_base?
2903      if @interp._eval_without_enc("catch {::safe::interpConfigure #{name}}") == '0'
2904        begin
2905          @interp._eval_without_enc("::safe::interpDelete #{name}")
2906        rescue Exception
2907        else
2908          next if subip.deleted?
2909        end
2910      end
2911      if subip.respond_to?(:safe_base?) && subip.safe_base? &&
2912          !subip.deleted?
2913        # do 'exit' to call the delete_hook procedure
2914        begin
2915          subip._eval_without_enc('exit')
2916        rescue Exception
2917        end
2918      else
2919        begin
2920          subip.delete unless subip.deleted?
2921        rescue Exception
2922        end
2923      end
2924    }
2925
2926    begin
2927      # @interp._eval_without_enc("foreach i [after info] {after cancel $i}")
2928      after_ids = @interp._eval_without_enc("after info")
2929      @interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
2930    rescue Exception
2931    end
2932
2933    begin
2934      @interp._invoke('destroy', '.') unless @interp.deleted?
2935    rescue Exception
2936    end
2937
2938    if @safe_base && !@interp.deleted?
2939      # do 'exit' to call the delete_hook procedure
2940      @interp._eval_without_enc('exit')
2941    end
2942    @interp.delete
2943    self
2944  end
2945
2946  def deleted?
2947    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2948    @interp.deleted?
2949  end
2950
2951  def has_mainwindow?
2952    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2953    @interp.has_mainwindow?
2954  end
2955
2956  def invalid_namespace?
2957    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2958    @interp.invalid_namespace?
2959  end
2960
2961  def abort(msg = nil)
2962    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2963    if master? && !safe? && allow_ruby_exit?
2964      if msg
2965        Kernel.abort(msg)
2966      else
2967        Kernel.abort
2968      end
2969    else
2970      # ignore msg
2971      delete
2972      1
2973    end
2974  end
2975
2976  def exit(st = true)
2977    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2978    if master? && !safe? && allow_ruby_exit?
2979      Kernel.exit(st)
2980    else
2981      delete
2982      st
2983    end
2984  end
2985
2986  def exit!(st = false)
2987    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2988    if master? && !safe? && allow_ruby_exit?
2989      Kernel.exit!(st)
2990    else
2991      delete
2992      st
2993    end
2994  end
2995
2996  def restart(app_name = nil, keys = {})
2997    raise SecurityError, "no permission to manipulate" unless self.manipulable?
2998
2999    _init_ip_internal(@@INIT_IP_ENV, @@ADD_TK_PROCS)
3000
3001    @interp._invoke('set', 'argv0', app_name) if app_name
3002    if keys.kind_of?(Hash)
3003      @interp._invoke('set', 'argv', _keys2opts(keys))
3004    end
3005
3006    @interp.restart
3007  end
3008
3009  def __eval(str)
3010    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3011    @interp.__eval(str)
3012  end
3013
3014  def __invoke(*args)
3015    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3016    @interp.__invoke(*args)
3017  end
3018
3019  def _eval(str)
3020    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3021    @interp._eval(str)
3022  end
3023
3024  def _invoke(*args)
3025    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3026    @interp._invoke(*args)
3027  end
3028
3029  def _eval_without_enc(str)
3030    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3031    @interp._eval_without_enc(str)
3032  end
3033
3034  def _invoke_without_enc(*args)
3035    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3036    @interp._invoke_without_enc(*args)
3037  end
3038
3039  def _eval_with_enc(str)
3040    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3041    @interp._eval_with_enc(str)
3042  end
3043
3044  def _invoke_with_enc(*args)
3045    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3046    @interp._invoke_with_enc(*args)
3047  end
3048
3049  def _toUTF8(str, encoding=nil)
3050    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3051    @interp._toUTF8(str, encoding)
3052  end
3053
3054  def _fromUTF8(str, encoding=nil)
3055    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3056    @interp._fromUTF8(str, encoding)
3057  end
3058
3059  def _thread_vwait(var)
3060    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3061    @interp._thread_vwait(var)
3062  end
3063
3064  def _thread_tkwait(mode, target)
3065    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3066    @interp._thread_tkwait(mode, target)
3067  end
3068
3069  def _return_value
3070    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3071    @interp._return_value
3072  end
3073
3074  def _get_variable(var, flag)
3075    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3076    @interp._get_variable(var, flag)
3077  end
3078  def _get_variable2(var, idx, flag)
3079    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3080    @interp._get_variable2(var, idx, flag)
3081  end
3082  def _set_variable(var, value, flag)
3083    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3084    @interp._set_variable(var, value, flag)
3085  end
3086  def _set_variable2(var, idx, value, flag)
3087    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3088    @interp._set_variable2(var, idx, value, flag)
3089  end
3090  def _unset_variable(var, flag)
3091    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3092    @interp._unset_variable(var, flag)
3093  end
3094  def _unset_variable2(var, idx, flag)
3095    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3096    @interp._unset_variable2(var, idx, flag)
3097  end
3098
3099  def _get_global_var(var)
3100    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3101    @interp._get_global_var(var)
3102  end
3103  def _get_global_var2(var, idx)
3104    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3105    @interp._get_global_var2(var, idx)
3106  end
3107  def _set_global_var(var, value)
3108    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3109    @interp._set_global_var(var, value)
3110  end
3111  def _set_global_var2(var, idx, value)
3112    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3113    @interp._set_global_var2(var, idx, value)
3114  end
3115  def _unset_global_var(var)
3116    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3117    @interp._unset_global_var(var)
3118  end
3119  def _unset_global_var2(var, idx)
3120    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3121    @interp._unset_global_var2(var, idx)
3122  end
3123
3124  def _make_menu_embeddable(menu_path)
3125    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3126    @interp._make_menu_embeddable(menu_path)
3127  end
3128
3129  def _split_tklist(str)
3130    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3131    @interp._split_tklist(str)
3132  end
3133  def _merge_tklist(*args)
3134    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3135    @interp._merge_tklist(*args)
3136  end
3137  def _conv_listelement(arg)
3138    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3139    @interp._conv_listelement(arg)
3140  end
3141end
3142
3143
3144# interp command support
3145class MultiTkIp
3146  def _lst2ary(str)
3147    return [] if str == ""
3148    idx = str.index('{')
3149    while idx and idx > 0 and str[idx-1] == ?\\
3150      idx = str.index('{', idx+1)
3151    end
3152    return str.split unless idx
3153
3154    list = str[0,idx].split
3155    str = str[idx+1..-1]
3156    i = -1
3157    brace = 1
3158    str.each_byte {|c|
3159      c = c.chr
3160      i += 1
3161      brace += 1 if c == '{'
3162      brace -= 1 if c == '}'
3163      break if brace == 0
3164    }
3165    if i == 0
3166      list.push ''
3167    elsif str[0, i] == ' '
3168      list.push ' '
3169    else
3170      list.push str[0..i-1]
3171    end
3172    #list += _lst2ary(str[i+1..-1])
3173    list.concat(_lst2ary(str[i+1..-1]))
3174    list
3175  end
3176  private :_lst2ary
3177
3178  def _slavearg(slave)
3179    if slave.kind_of?(MultiTkIp)
3180      slave.path
3181    elsif slave.kind_of?(String)
3182      slave
3183    else
3184      slave.to_s
3185    end
3186  end
3187  private :_slavearg
3188
3189  def alias_info(slave, cmd_name)
3190    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3191    _lst2ary(@interp._invoke('interp', 'alias', _slavearg(slave), cmd_name))
3192  end
3193  def self.alias_info(slave, cmd_name)
3194    __getip.alias_info(slave, cmd_name)
3195  end
3196
3197  def alias_delete(slave, cmd_name)
3198    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3199    @interp._invoke('interp', 'alias', _slavearg(slave), cmd_name, '')
3200    self
3201  end
3202  def self.alias_delete(slave, cmd_name)
3203    __getip.alias_delete(slave, cmd_name)
3204    self
3205  end
3206
3207  def def_alias(slave, new_cmd, org_cmd, *args)
3208    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3209    ret = @interp._invoke('interp', 'alias', _slavearg(slave), new_cmd,
3210                          '', org_cmd, *args)
3211    (ret == new_cmd)? self: nil
3212  end
3213  def self.def_alias(slave, new_cmd, org_cmd, *args)
3214    ret = __getip.def_alias(slave, new_cmd, org_cmd, *args)
3215    (ret == new_cmd)? self: nil
3216  end
3217
3218  def aliases(slave = '')
3219    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3220    _lst2ary(@interp._invoke('interp', 'aliases', _slavearg(slave)))
3221  end
3222  def self.aliases(slave = '')
3223    __getip.aliases(slave)
3224  end
3225
3226  def delete_slaves(*args)
3227    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3228    slaves = args.collect{|s| _slavearg(s)}
3229    @interp._invoke('interp', 'delete', *slaves) if slaves.size > 0
3230    self
3231  end
3232  def self.delete_slaves(*args)
3233    __getip.delete_slaves(*args)
3234    self
3235  end
3236
3237  def exist?(slave = '')
3238    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3239    ret = @interp._invoke('interp', 'exists', _slavearg(slave))
3240    (ret == '1')? true: false
3241  end
3242  def self.exist?(slave = '')
3243    __getip.exist?(slave)
3244  end
3245
3246  def delete_cmd(slave, cmd)
3247    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3248    slave_invoke = @interp._invoke('list', 'rename', cmd, '')
3249    @interp._invoke('interp', 'eval', _slavearg(slave), slave_invoke)
3250    self
3251  end
3252  def self.delete_cmd(slave, cmd)
3253    __getip.delete_cmd(slave, cmd)
3254    self
3255  end
3256
3257  def expose_cmd(slave, cmd, aliasname = nil)
3258    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3259    if aliasname
3260      @interp._invoke('interp', 'expose', _slavearg(slave), cmd, aliasname)
3261    else
3262      @interp._invoke('interp', 'expose', _slavearg(slave), cmd)
3263    end
3264    self
3265  end
3266  def self.expose_cmd(slave, cmd, aliasname = nil)
3267    __getip.expose_cmd(slave, cmd, aliasname)
3268    self
3269  end
3270
3271  def hide_cmd(slave, cmd, aliasname = nil)
3272    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3273    if aliasname
3274      @interp._invoke('interp', 'hide', _slavearg(slave), cmd, aliasname)
3275    else
3276      @interp._invoke('interp', 'hide', _slavearg(slave), cmd)
3277    end
3278    self
3279  end
3280  def self.hide_cmd(slave, cmd, aliasname = nil)
3281    __getip.hide_cmd(slave, cmd, aliasname)
3282    self
3283  end
3284
3285  def hidden_cmds(slave = '')
3286    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3287    _lst2ary(@interp._invoke('interp', 'hidden', _slavearg(slave)))
3288  end
3289  def self.hidden_cmds(slave = '')
3290    __getip.hidden_cmds(slave)
3291  end
3292
3293  def invoke_hidden(slave, cmd, *args)
3294    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3295    if args[-1].kind_of?(Hash)
3296      keys = _symbolkey2str(args.pop)
3297    else
3298      keys = []
3299    end
3300    keys << _slavearg(slave)
3301    if Tk::TCL_MAJOR_VERSION > 8 ||
3302        (Tk::TCL_MAJOR_VERSION == 8 && Tk::TCL_MINOR_VERSION >= 5)
3303      keys << '--'
3304    end
3305    keys << cmd
3306    keys.concat(args)
3307    @interp._invoke('interp', 'invokehidden', *keys)
3308  end
3309  def self.invoke_hidden(slave, cmd, *args)
3310    __getip.invoke_hidden(slave, cmd, *args)
3311  end
3312
3313  def invoke_hidden_on_global(slave, cmd, *args)
3314    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3315    if args[-1].kind_of?(Hash)
3316      keys = _symbolkey2str(args.pop)
3317    else
3318      keys = []
3319    end
3320    keys << _slavearg(slave)
3321    keys << '-global'
3322    if Tk::TCL_MAJOR_VERSION > 8 ||
3323        (Tk::TCL_MAJOR_VERSION == 8 && Tk::TCL_MINOR_VERSION >= 5)
3324      keys << '--'
3325    end
3326    keys << cmd
3327    keys.concat(args)
3328    @interp._invoke('interp', 'invokehidden', *keys)
3329  end
3330  def self.invoke_hidden_on_global(slave, cmd, *args)
3331    __getip.invoke_hidden_on_global(slave, cmd, *args)
3332  end
3333
3334  def invoke_hidden_on_namespace(slave, ns, cmd, *args)
3335    # for Tcl8.5 or later
3336    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3337    if args[-1].kind_of?(Hash)
3338      keys = _symbolkey2str(args.pop)
3339    else
3340      keys = []
3341    end
3342    keys << _slavearg(slave)
3343    keys << '-namespace' << TkComm._get_eval_string(ns)
3344    keys << '--' << cmd
3345    keys.concat(args)
3346    @interp._invoke('interp', 'invokehidden', *keys)
3347  end
3348  def self.invoke_hidden_on_namespace(slave, ns, cmd, *args)
3349    __getip.invoke_hidden_on_namespace(slave, ns, cmd, *args)
3350  end
3351
3352  def mark_trusted(slave = '')
3353    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3354    @interp._invoke('interp', 'marktrusted', _slavearg(slave))
3355    self
3356  end
3357  def self.mark_trusted(slave = '')
3358    __getip.mark_trusted(slave)
3359    self
3360  end
3361
3362  def set_bgerror_handler(cmd = Proc.new, slave = nil, &b)
3363    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3364
3365    unless TkComm._callback_entry?(cmd)
3366      if !slave && b
3367        slave = cmd
3368        cmd = Proc.new(&b)
3369      end
3370    end
3371    slave = '' unless slave
3372
3373    @interp._invoke('interp', 'bgerror', _slavearg(slave), cmd)
3374  end
3375  def self.bgerror(cmd = Proc.new, slave = nil, &b)
3376    __getip.bgerror(cmd, slave, &b)
3377  end
3378
3379  def get_bgerror_handler(slave = '')
3380    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3381    procedure(@interp._invoke('interp', 'bgerror', _slavearg(slave)))
3382  end
3383  def self.bgerror(slave = '')
3384    __getip.bgerror(slave)
3385  end
3386
3387  def set_limit(limit_type, slave = '', opts = {})
3388    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3389    @interp._invoke('interp', 'limit', _slavearg(slave), limit_type, opts)
3390  end
3391  def self.set_limit(limit_type, slave = '', opts = {})
3392    __getip.set_limit(limit_type, slave, opts)
3393  end
3394
3395  def get_limit(limit_type, slave = '', slot = nil)
3396    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3397
3398    if slot
3399      num_or_str(@interp._invoke('interp', 'limit', _slavearg(slave),
3400                                 limit_type, slot))
3401    else
3402      l = @interp._split_tklist(@interp._invoke_without_enc('interp', 'limit',
3403                                                            _slavearg(slave),
3404                                                            limit_type))
3405      l.map!{|s| _fromUTF8(s)}
3406      r = {}
3407      until l.empty?
3408        key = l.shift[1..-1]
3409        val = l.shift
3410        val = num_or_str(val) if val
3411        r[key] = val
3412      end
3413      r
3414    end
3415  end
3416  def self.get_limit(limit_type, slave = '', slot = nil)
3417    __getip.get_limit(limit_type, slave, slot)
3418  end
3419
3420  def recursion_limit(slave = '', limit = None)
3421    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3422    number(@interp._invoke('interp', 'recursionlimit',
3423                           _slavearg(slave), limit))
3424  end
3425  def self.recursion_limit(slave = '', limit = None)
3426    __getip.recursion_limit(slave)
3427  end
3428
3429  def alias_target(aliascmd, slave = '')
3430    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3431    @interp._invoke('interp', 'target', _slavearg(slave), aliascmd)
3432  end
3433  def self.alias_target(aliascmd, slave = '')
3434    __getip.alias_target(aliascmd, slave)
3435  end
3436
3437  def share_stdin(dist, src = '')
3438    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3439    @interp._invoke('interp', 'share', src, 'stdin', dist)
3440    self
3441  end
3442  def self.share_stdin(dist, src = '')
3443    __getip.share_stdin(dist, src)
3444    self
3445  end
3446
3447  def share_stdout(dist, src = '')
3448    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3449    @interp._invoke('interp', 'share', src, 'stdout', dist)
3450    self
3451  end
3452  def self.share_stdout(dist, src = '')
3453    __getip.share_stdout(dist, src)
3454    self
3455  end
3456
3457  def share_stderr(dist, src = '')
3458    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3459    @interp._invoke('interp', 'share', src, 'stderr', dist)
3460    self
3461  end
3462  def self.share_stderr(dist, src = '')
3463    __getip.share_stderr(dist, src)
3464    self
3465  end
3466
3467  def transfer_stdin(dist, src = '')
3468    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3469    @interp._invoke('interp', 'transfer', src, 'stdin', dist)
3470    self
3471  end
3472  def self.transfer_stdin(dist, src = '')
3473    __getip.transfer_stdin(dist, src)
3474    self
3475  end
3476
3477  def transfer_stdout(dist, src = '')
3478    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3479    @interp._invoke('interp', 'transfer', src, 'stdout', dist)
3480    self
3481  end
3482  def self.transfer_stdout(dist, src = '')
3483    __getip.transfer_stdout(dist, src)
3484    self
3485  end
3486
3487  def transfer_stderr(dist, src = '')
3488    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3489    @interp._invoke('interp', 'transfer', src, 'stderr', dist)
3490    self
3491  end
3492  def self.transfer_stderr(dist, src = '')
3493    __getip.transfer_stderr(dist, src)
3494    self
3495  end
3496
3497  def share_stdio(dist, src = '')
3498    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3499    @interp._invoke('interp', 'share', src, 'stdin',  dist)
3500    @interp._invoke('interp', 'share', src, 'stdout', dist)
3501    @interp._invoke('interp', 'share', src, 'stderr', dist)
3502    self
3503  end
3504  def self.share_stdio(dist, src = '')
3505    __getip.share_stdio(dist, src)
3506    self
3507  end
3508
3509  def transfer_stdio(dist, src = '')
3510    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3511    @interp._invoke('interp', 'transfer', src, 'stdin',  dist)
3512    @interp._invoke('interp', 'transfer', src, 'stdout', dist)
3513    @interp._invoke('interp', 'transfer', src, 'stderr', dist)
3514    self
3515  end
3516  def self.transfer_stdio(dist, src = '')
3517    __getip.transfer_stdio(dist, src)
3518    self
3519  end
3520end
3521
3522
3523# Safe Base :: manipulating safe interpreter
3524class MultiTkIp
3525  def safeip_configure(slot, value=None)
3526    # use for '-noStatics' option ==> {statics=>false}
3527    #     for '-nestedLoadOk' option ==> {nested=>true}
3528    if slot.kind_of?(Hash)
3529      ip = MultiTkIp.__getip
3530      ip._eval('::safe::interpConfigure ' + @ip_name + ' ' + _keys2opts(slot))
3531    else
3532      ip._eval('::safe::interpConfigure ' + @ip_name + ' ' +
3533               "-#{slot} #{_get_eval_string(value)}")
3534    end
3535    self
3536  end
3537
3538  def safeip_configinfo(slot = nil)
3539    ip = MultiTkIp.__getip
3540    ret = {}
3541    if slot
3542      conf = _lst2ary(ip._eval("::safe::interpConfigure " +
3543                               @ip_name + " -#{slot}"))
3544      if conf[0] == '-deleteHook'
3545=begin
3546        if conf[1] =~ /^rb_out\S* (c(_\d+_)?\d+)/
3547          ret[conf[0][1..-1]] = MultiTkIp._tk_cmd_tbl[$1]
3548=end
3549        if conf[1] =~ /rb_out\S*(?:\s+(::\S*|[{](::.*)[}]|["](::.*)["]))? (c(_\d+_)?(\d+))/
3550          ret[conf[0][1..-1]] = MultiTkIp._tk_cmd_tbl[$4]
3551        else
3552          ret[conf[0][1..-1]] = conf[1]
3553        end
3554      else
3555        ret[conf[0][1..-1]] = conf[1]
3556      end
3557    else
3558      Hash[*_lst2ary(ip._eval("::safe::interpConfigure " +
3559                              @ip_name))].each{|k, v|
3560        if k == '-deleteHook'
3561=begin
3562          if v =~ /^rb_out\S* (c(_\d+_)?\d+)/
3563            ret[k[1..-1]] = MultiTkIp._tk_cmd_tbl[$1]
3564=end
3565          if v =~ /rb_out\S*(?:\s+(::\S*|[{](::.*)[}]|["](::.*)["]))? (c(_\d+_)?(\d+))/
3566            ret[k[1..-1]] = MultiTkIp._tk_cmd_tbl[$4]
3567          else
3568            ret[k[1..-1]] = v
3569          end
3570        else
3571          ret[k[1..-1]] = v
3572        end
3573      }
3574    end
3575    ret
3576  end
3577
3578  def safeip_delete
3579    ip = MultiTkIp.__getip
3580    ip._eval("::safe::interpDelete " + @ip_name)
3581  end
3582
3583  def safeip_add_to_access_path(dir)
3584    ip = MultiTkIp.__getip
3585    ip._eval("::safe::interpAddToAccessPath #{@ip_name} #{dir}")
3586  end
3587
3588  def safeip_find_in_access_path(dir)
3589    ip = MultiTkIp.__getip
3590    ip._eval("::safe::interpFindInAccessPath #{@ip_name} #{dir}")
3591  end
3592
3593  def safeip_set_log_cmd(cmd = Proc.new)
3594    ip = MultiTkIp.__getip
3595    ip._eval("::safe::setLogCmd #{@ip_name} #{_get_eval_string(cmd)}")
3596  end
3597end
3598
3599
3600# encoding convert
3601class << MultiTkIp
3602  def encoding_table
3603    __getip.encoding_table
3604  end
3605
3606  def force_default_encoding=(mode)
3607    __getip.force_default_encoding=(mode)
3608  end
3609
3610  def force_default_encoding?
3611    __getip.force_default_encoding?
3612  end
3613
3614  def default_encoding=(enc)
3615    __getip.default_encoding=(enc)
3616  end
3617
3618  def encoding=(enc)
3619    __getip.encoding=(enc)
3620  end
3621
3622  def encoding_name
3623    __getip.encoding_name
3624  end
3625
3626  def encoding_obj
3627    __getip.encoding_obj
3628  end
3629  alias encoding encoding_name
3630  alias default_encoding encoding_name
3631
3632  def encoding_convertfrom(str, enc=None)
3633    __getip.encoding_convertfrom(str, enc)
3634  end
3635  alias encoding_convert_from encoding_convertfrom
3636
3637  def encoding_convertto(str, enc=None)
3638    __getip.encoding_convertto(str, enc)
3639  end
3640  alias encoding_convert_to encoding_convertto
3641end
3642class MultiTkIp
3643  def encoding_table
3644    @interp.encoding_table
3645  end
3646
3647  def force_default_encoding=(mode)
3648    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3649    @interp.force_default_encoding = mode
3650  end
3651  def force_default_encoding?
3652    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3653    @interp.force_default_encoding?
3654  end
3655
3656  def default_encoding=(enc)
3657    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3658    @interp.default_encoding = enc
3659  end
3660
3661  def encoding=(enc)
3662    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3663    @interp.encoding = enc
3664  end
3665  def encoding_name
3666    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3667    @interp.encoding_name
3668  end
3669  def encoding_obj
3670    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3671    @interp.encoding_obj
3672  end
3673  alias encoding encoding_name
3674  alias default_encoding encoding_name
3675
3676  def encoding_convertfrom(str, enc=None)
3677    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3678    @interp.encoding_convertfrom(str, enc)
3679  end
3680  alias encoding_convert_from encoding_convertfrom
3681
3682  def encoding_convertto(str, enc=None)
3683    raise SecurityError, "no permission to manipulate" unless self.manipulable?
3684    @interp.encoding_convertto(str, enc)
3685  end
3686  alias encoding_convert_to encoding_convertto
3687end
3688
3689
3690# remove methods for security
3691=begin
3692class MultiTkIp
3693  INTERP_THREAD = @@DEFAULT_MASTER.instance_variable_get('@interp_thread')
3694  INTERP_MUTEX  = INTERP_THREAD[:mutex]
3695  INTERP_ROOT_CHECK = INTERP_THREAD[:root_check]
3696
3697  # undef_method :instance_eval
3698  undef_method :instance_variable_get
3699  undef_method :instance_variable_set
3700end
3701
3702module TkCore
3703  if MultiTkIp::WITH_RUBY_VM &&
3704      ! MultiTkIp::RUN_EVENTLOOP_ON_MAIN_THREAD ### check Ruby 1.9 !!!!!!!
3705    INTERP_THREAD = MultiTkIp::INTERP_THREAD
3706    INTERP_MUTEX  = MultiTkIp::INTERP_MUTEX
3707    INTERP_ROOT_CHECK = MultiTkIp::INTERP_ROOT_CHECK
3708  end
3709end
3710class MultiTkIp
3711  remove_const(:INTERP_THREAD)
3712  remove_const(:INTERP_MUTEX)
3713  remove_const(:INTERP_ROOT_CHECK)
3714end
3715=end
3716if MultiTkIp::WITH_RUBY_VM &&
3717    ! MultiTkIp::RUN_EVENTLOOP_ON_MAIN_THREAD ### check Ruby 1.9 !!!!!!!
3718  class MultiTkIp
3719    INTERP_THREAD = @@DEFAULT_MASTER.instance_variable_get('@interp_thread')
3720    INTERP_THREAD_STATUS = INTERP_THREAD[:status]
3721    INTERP_MUTEX  = INTERP_THREAD[:mutex]
3722    INTERP_ROOT_CHECK = INTERP_THREAD[:root_check]
3723  end
3724  module TkCore
3725    INTERP_THREAD = MultiTkIp::INTERP_THREAD
3726    INTERP_THREAD_STATUS = MultiTkIp::INTERP_THREAD_STATUS
3727    INTERP_MUTEX  = MultiTkIp::INTERP_MUTEX
3728    INTERP_ROOT_CHECK = MultiTkIp::INTERP_ROOT_CHECK
3729  end
3730  class MultiTkIp
3731    remove_const(:INTERP_THREAD)
3732    remove_const(:INTERP_THREAD_STATUS)
3733    remove_const(:INTERP_MUTEX)
3734    remove_const(:INTERP_ROOT_CHECK)
3735  end
3736end
3737
3738class MultiTkIp
3739  # undef_method :instance_eval
3740  undef_method :instance_variable_get
3741  undef_method :instance_variable_set
3742end
3743# end of MultiTkIp definition
3744
3745# defend against modification
3746#MultiTkIp.freeze
3747#TclTkLib.freeze
3748
3749########################################
3750#  start Tk which depends on MultiTkIp
3751module TkCore
3752  INTERP = MultiTkIp
3753end
3754require 'tk'
3755