1#
2#               remote-tk.rb - supports to control remote Tk interpreters
3#                       by Hidetoshi NAGAI <nagai@ai.kyutech.ac.jp>
4
5if defined? MultiTkIp
6  fail RuntimeError, "'remote-tk' library must be required before requiring 'multi-tk'"
7end
8
9class MultiTkIp; end
10class RemoteTkIp < MultiTkIp; end
11
12class MultiTkIp
13  @@IP_TABLE = TkUtil.untrust({}) unless defined?(@@IP_TABLE)
14  @@TK_TABLE_LIST = TkUtil.untrust([]) unless defined?(@@TK_TABLE_LIST)
15  def self._IP_TABLE; @@IP_TABLE; end
16  def self._TK_TABLE_LIST; @@TK_TABLE_LIST; end
17
18  @flag = true
19  def self._DEFAULT_MASTER
20    # work only once
21    if @flag
22      @flag = nil
23      @@DEFAULT_MASTER
24    else
25      nil
26    end
27  end
28end
29class RemoteTkIp
30  @@IP_TABLE = MultiTkIp._IP_TABLE unless defined?(@@IP_TABLE)
31  @@TK_TABLE_LIST = MultiTkIp._TK_TABLE_LIST unless defined?(@@TK_TABLE_LIST)
32end
33class << MultiTkIp
34  undef _IP_TABLE
35  undef _TK_TABLE_LIST
36end
37
38require 'multi-tk'
39
40class RemoteTkIp
41  if defined?(@@DEFAULT_MASTER)
42    MultiTkIp._DEFAULT_MASTER
43  else
44    @@DEFAULT_MASTER = MultiTkIp._DEFAULT_MASTER
45  end
46end
47
48
49###############################
50
51class << RemoteTkIp
52  undef new_master, new_slave, new_safe_slave
53  undef new_trusted_slave, new_safeTk
54
55  def new(*args, &b)
56    ip = __new(*args)
57    ip.eval_proc(&b) if b
58    ip
59  end
60end
61
62class RemoteTkIp
63  def initialize(remote_ip, displayof=nil, timeout=5)
64    if $SAFE >= 4
65      fail SecurityError, "cannot access another interpreter at level #{$SAFE}"
66    end
67
68    @interp = MultiTkIp.__getip
69    if @interp.safe?
70      fail SecurityError, "safe-IP cannot create RemoteTkIp"
71    end
72
73
74    @interp.allow_ruby_exit = false
75    @appname = @interp._invoke('tk', 'appname')
76    @remote = remote_ip.to_s.dup.freeze
77    if displayof.kind_of?(TkWindow)
78      @displayof = displayof.path.dup.freeze
79    else
80      @displayof = nil
81    end
82    if self.deleted?
83      fail RuntimeError, "no Tk application named \"#{@remote}\""
84    end
85
86    @tk_windows = {}
87    @tk_table_list = []
88    @slave_ip_tbl = {}
89    @slave_ip_top = {}
90
91    @force_default_encoding ||= TkUtil.untrust([false])
92    @encoding ||= TkUtil.untrust([nil])
93    def @encoding.to_s; self.join(nil); end
94
95    TkUtil.untrust(@tk_windows)    unless @tk_windows.tainted?
96    TkUtil.untrust(@tk_table_list) unless @tk_table_list.tainted?
97    TkUtil.untrust(@slave_ip_tbl)  unless @slave_ip_tbl.tainted?
98    TkUtil.untrust(@slave_ip_top)  unless @slave_ip_top.tainted?
99
100    @system = Object.new
101
102    @threadgroup  = ThreadGroup.new
103
104    @safe_level = [$SAFE]
105
106    @wait_on_mainloop = [true, 0]
107
108    @cmd_queue = Queue.new
109
110=begin
111    @cmd_receiver, @receiver_watchdog = _create_receiver_and_watchdog()
112
113    @threadgroup.add @cmd_receiver
114    @threadgroup.add @receiver_watchdog
115
116    @threadgroup.enclose
117=end
118    @@DEFAULT_MASTER.assign_receiver_and_watchdog(self)
119
120    @@IP_TABLE[@threadgroup] = self
121    @@TK_TABLE_LIST.size.times{
122      (tbl = {}).tainted? || TkUtil.untrust(tbl)
123      @tk_table_list << tbl
124    }
125
126    @ret_val = TkVariable.new
127    if timeout > 0 && ! _available_check(timeout)
128      fail RuntimeError, "cannot create connection"
129    end
130    @ip_id = _create_connection
131
132    class << self
133      undef :instance_eval
134    end
135
136    self.freeze  # defend against modification
137  end
138
139  def manipulable?
140    return true if (Thread.current.group == ThreadGroup::Default)
141    MultiTkIp.__getip == @interp && ! @interp.safe?
142  end
143  def self.manipulable?
144    true
145  end
146
147  def _is_master_of?(tcltkip_obj)
148    tcltkip_obj == @interp
149  end
150  protected :_is_master_of?
151
152  def _ip_id_
153    @ip_id
154  end
155
156  def _available_check(timeout = 5)
157    raise SecurityError, "no permission to manipulate" unless self.manipulable?
158
159    return nil if timeout < 1
160    @ret_val.value = ''
161    @interp._invoke('send', '-async', @remote,
162                    'send', '-async', Tk.appname,
163                    "set #{@ret_val.id} ready")
164    Tk.update
165    if @ret_val != 'ready'
166      (1..(timeout*5)).each{
167        sleep 0.2
168        Tk.update
169        break if @ret_val == 'ready'
170      }
171    end
172    @ret_val.value == 'ready'
173  end
174  private :_available_check
175
176  def _create_connection
177    raise SecurityError, "no permission to manipulate" unless self.manipulable?
178
179    ip_id = '_' + @interp._invoke('send', @remote, <<-'EOS') + '_'
180      if {[catch {set _rubytk_control_ip_id_} ret] != 0} {
181        set _rubytk_control_ip_id_ 0
182      } else {
183        set _rubytk_control_ip_id_ [expr $ret + 1]
184      }
185      return $_rubytk_control_ip_id_
186    EOS
187
188    @interp._invoke('send', @remote, <<-EOS)
189      proc rb_out#{ip_id} args {
190        send #{@appname} rb_out \$args
191      }
192    EOS
193
194    ip_id
195  end
196  private :_create_connection
197
198  def _appsend(enc_mode, async, *cmds)
199    raise SecurityError, "no permission to manipulate" unless self.manipulable?
200
201    p ['_appsend', [@remote, @displayof], enc_mode, async, cmds] if $DEBUG
202    if $SAFE >= 4
203      fail SecurityError, "cannot send commands at level 4"
204    elsif $SAFE >= 1 && cmds.find{|obj| obj.tainted?}
205      fail SecurityError, "cannot send tainted commands at level #{$SAFE}"
206    end
207
208    cmds = @interp._merge_tklist(*TkUtil::_conv_args([], enc_mode, *cmds))
209    if @displayof
210      if async
211        @interp.__invoke('send', '-async', '-displayof', @displayof,
212                         '--', @remote, *cmds)
213      else
214        @interp.__invoke('send', '-displayof', @displayof,
215                         '--', @remote, *cmds)
216      end
217    else
218      if async
219        @interp.__invoke('send', '-async', '--', @remote, *cmds)
220      else
221        @interp.__invoke('send', '--', @remote, *cmds)
222      end
223    end
224  end
225  private :_appsend
226
227  def ready?(timeout=5)
228    if timeout < 0
229      fail ArgumentError, "timeout must be positive number"
230    end
231    _available_check(timeout)
232  end
233
234  def is_rubytk?
235    return false if _appsend(false, false, 'info', 'command', 'ruby') == ""
236    [ _appsend(false, false, 'ruby', 'RUBY_VERSION'),
237      _appsend(false, false, 'set', 'tk_patchLevel') ]
238  end
239
240  def appsend(async, *args)
241    raise SecurityError, "no permission to manipulate" unless self.manipulable?
242
243    if async != true && async != false && async != nil
244      args.unshift(async)
245      async = false
246    end
247    if @displayof
248      Tk.appsend_displayof(@remote, @displayof, async, *args)
249    else
250      Tk.appsend(@remote, async, *args)
251    end
252  end
253
254  def rb_appsend(async, *args)
255    raise SecurityError, "no permission to manipulate" unless self.manipulable?
256
257    if async != true && async != false && async != nil
258      args.unshift(async)
259      async = false
260    end
261    if @displayof
262      Tk.rb_appsend_displayof(@remote, @displayof, async, *args)
263    else
264      Tk.rb_appsend(@remote, async, *args)
265    end
266  end
267
268  def create_slave(name, safe=false)
269    if safe
270      safe_opt = ''
271    else
272      safe_opt = '-safe'
273    end
274    _appsend(false, false, "interp create #{safe_opt} -- #{name}")
275  end
276
277  def make_safe
278    fail RuntimeError, 'cannot change safe mode of the remote interpreter'
279  end
280
281  def safe?
282    _appsend(false, false, 'interp issafe')
283  end
284
285  def safe_base?
286    false
287  end
288
289  def allow_ruby_exit?
290    false
291  end
292
293  def allow_ruby_exit= (mode)
294    fail RuntimeError, 'cannot change mode of the remote interpreter'
295  end
296
297  def delete
298    _appsend(false, true, 'exit')
299  end
300
301  def deleted?
302    raise SecurityError, "no permission to manipulate" unless self.manipulable?
303
304    if @displayof
305      lst = @interp._invoke_without_enc('winfo', 'interps',
306                                        '-displayof', @displayof)
307    else
308      lst = @interp._invoke_without_enc('winfo', 'interps')
309    end
310    # unless @interp._split_tklist(lst).index(@remote)
311    unless @interp._split_tklist(lst).index(_toUTF8(@remote))
312      true
313    else
314      false
315    end
316  end
317
318  def has_mainwindow?
319    raise SecurityError, "no permission to manipulate" unless self.manipulable?
320
321    begin
322      inf = @interp._invoke_without_enc('info', 'command', '.')
323    rescue Exception
324      return nil
325    end
326    if !inf.kind_of?(String) || inf != '.'
327      false
328    else
329      true
330    end
331  end
332
333  def invalid_namespace?
334    false
335  end
336
337  def restart
338    fail RuntimeError, 'cannot restart the remote interpreter'
339  end
340
341  def __eval(str)
342    _appsend(false, false, str)
343  end
344  def _eval(str)
345    _appsend(nil, false, str)
346  end
347  def _eval_without_enc(str)
348    _appsend(false, false, str)
349  end
350  def _eval_with_enc(str)
351    _appsend(true, false, str)
352  end
353
354  def _invoke(*args)
355    _appsend(nil, false, *args)
356  end
357
358  def __invoke(*args)
359    _appsend(false, false, *args)
360  end
361  def _invoke(*args)
362    _appsend(nil, false, *args)
363  end
364  def _invoke_without_enc(*args)
365    _appsend(false, false, *args)
366  end
367  def _invoke_with_enc(*args)
368    _appsend(true, false, *args)
369  end
370
371  def _toUTF8(str, encoding=nil)
372    raise SecurityError, "no permission to manipulate" unless self.manipulable?
373    @interp._toUTF8(str, encoding)
374  end
375
376  def _fromUTF8(str, encoding=nil)
377    raise SecurityError, "no permission to manipulate" unless self.manipulable?
378    @interp._fromUTF8(str, encoding)
379  end
380
381  def _thread_vwait(var_name)
382    _appsend(false, 'thread_vwait', varname)
383  end
384
385  def _thread_tkwait(mode, target)
386    _appsend(false, 'thread_tkwait', mode, target)
387  end
388
389  def _return_value
390    raise SecurityError, "no permission to manipulate" unless self.manipulable?
391    @interp._return_value
392  end
393
394  def _get_variable(var_name, flag)
395    # ignore flag
396    _appsend(false, 'set', TkComm::_get_eval_string(var_name))
397  end
398  def _get_variable2(var_name, index_name, flag)
399    # ignore flag
400    _appsend(false, 'set', "#{TkComm::_get_eval_string(var_name)}(#{TkComm::_get_eval_string(index_name)})")
401  end
402
403  def _set_variable(var_name, value, flag)
404    # ignore flag
405    _appsend(false, 'set', TkComm::_get_eval_string(var_name), TkComm::_get_eval_string(value))
406  end
407  def _set_variable2(var_name, index_name, value, flag)
408    # ignore flag
409    _appsend(false, 'set', "#{TkComm::_get_eval_string(var_name)}(#{TkComm::_get_eval_string(index_name)})", TkComm::_get_eval_string(value))
410  end
411
412  def _unset_variable(var_name, flag)
413    # ignore flag
414    _appsend(false, 'unset', TkComm::_get_eval_string(var_name))
415  end
416  def _unset_variable2(var_name, index_name, flag)
417    # ignore flag
418    _appsend(false, 'unset', "#{var_name}(#{index_name})")
419  end
420
421  def _get_global_var(var_name)
422    _appsend(false, 'set', TkComm::_get_eval_string(var_name))
423  end
424  def _get_global_var2(var_name, index_name)
425    _appsend(false, 'set', "#{TkComm::_get_eval_string(var_name)}(#{TkComm::_get_eval_string(index_name)})")
426  end
427
428  def _set_global_var(var_name, value)
429    _appsend(false, 'set', TkComm::_get_eval_string(var_name), TkComm::_get_eval_string(value))
430  end
431  def _set_global_var2(var_name, index_name, value)
432    _appsend(false, 'set', "#{TkComm::_get_eval_string(var_name)}(#{TkComm::_get_eval_string(index_name)})", TkComm::_get_eval_string(value))
433  end
434
435  def _unset_global_var(var_name)
436    _appsend(false, 'unset', TkComm::_get_eval_string(var_name))
437  end
438  def _unset_global_var2(var_name, index_name)
439    _appsend(false, 'unset', "#{var_name}(#{index_name})")
440  end
441
442  def _split_tklist(str)
443    raise SecurityError, "no permission to manipulate" unless self.manipulable?
444    @interp._split_tklist(str)
445  end
446
447  def _merge_tklist(*args)
448    raise SecurityError, "no permission to manipulate" unless self.manipulable?
449    @interp._merge_tklist(*args)
450  end
451
452  def _conv_listelement(str)
453    raise SecurityError, "no permission to manipulate" unless self.manipulable?
454    @interp._conv_listelement(str)
455  end
456
457  def _create_console
458    fail RuntimeError, 'not support "_create_console" on the remote interpreter'
459  end
460
461  def mainloop
462    fail RuntimeError, 'not support "mainloop" on the remote interpreter'
463  end
464  def mainloop_watchdog
465    fail RuntimeError, 'not support "mainloop_watchdog" on the remote interpreter'
466  end
467  def do_one_event(flag = nil)
468    fail RuntimeError, 'not support "do_one_event" on the remote interpreter'
469  end
470  def mainloop_abort_on_exception
471    fail RuntimeError, 'not support "mainloop_abort_on_exception" on the remote interpreter'
472  end
473  def mainloop_abort_on_exception=(mode)
474    fail RuntimeError, 'not support "mainloop_abort_on_exception=" on the remote interpreter'
475  end
476  def set_eventloop_tick(*args)
477    fail RuntimeError, 'not support "set_eventloop_tick" on the remote interpreter'
478  end
479  def get_eventloop_tick
480    fail RuntimeError, 'not support "get_eventloop_tick" on the remote interpreter'
481  end
482  def set_no_event_wait(*args)
483    fail RuntimeError, 'not support "set_no_event_wait" on the remote interpreter'
484  end
485  def get_no_event_wait
486    fail RuntimeError, 'not support "get_no_event_wait" on the remote interpreter'
487  end
488  def set_eventloop_weight(*args)
489    fail RuntimeError, 'not support "set_eventloop_weight" on the remote interpreter'
490  end
491  def get_eventloop_weight
492    fail RuntimeError, 'not support "get_eventloop_weight" on the remote interpreter'
493  end
494end
495
496class << RemoteTkIp
497  def mainloop(*args)
498    fail RuntimeError, 'not support "mainloop" on the remote interpreter'
499  end
500  def mainloop_watchdog(*args)
501    fail RuntimeError, 'not support "mainloop_watchdog" on the remote interpreter'
502  end
503  def do_one_event(flag = nil)
504    fail RuntimeError, 'not support "do_one_event" on the remote interpreter'
505  end
506  def mainloop_abort_on_exception
507    fail RuntimeError, 'not support "mainloop_abort_on_exception" on the remote interpreter'
508  end
509  def mainloop_abort_on_exception=(mode)
510    fail RuntimeError, 'not support "mainloop_abort_on_exception=" on the remote interpreter'
511  end
512  def set_eventloop_tick(*args)
513    fail RuntimeError, 'not support "set_eventloop_tick" on the remote interpreter'
514  end
515  def get_eventloop_tick
516    fail RuntimeError, 'not support "get_eventloop_tick" on the remote interpreter'
517  end
518  def set_no_event_wait(*args)
519    fail RuntimeError, 'not support "set_no_event_wait" on the remote interpreter'
520  end
521  def get_no_event_wait
522    fail RuntimeError, 'not support "get_no_event_wait" on the remote interpreter'
523  end
524  def set_eventloop_weight(*args)
525    fail RuntimeError, 'not support "set_eventloop_weight" on the remote interpreter'
526  end
527  def get_eventloop_weight
528    fail RuntimeError, 'not support "get_eventloop_weight" on the remote interpreter'
529  end
530end
531