Hi, 

I'm working on a framework of Ruby/Tk + VNC. 
Its purpose is to put GUI applications of Ruby/Tk on internet view.
The concept of the framework is, 

 * Use VNC (RFB protocol)

 * A GUI application is running on a safe (safe-Tk) based 
   slave interpreter (usually, $SAFE == 4).
   If required, you can also use a non-safe based slave.

 * Never use general window managers to decrease security risk.
   Except a VNC server, only one Ruby/Tk process is running.
   # In the future, it may be expected that Ruby/Tk has 
   # RFB server functions and no VNC server is required 
   # for this purpose.

 * A canvas widget of the master interpreter works like 
   as a window manager. The canvas widget administers the 
   slave interpreter's root/toplevel widgets by window items 
   embedded them.

By comparison with using tclplugin, probably, the followings 
are advantages.

 * If the user already has a kind of VNC viewer, 
   he can use it to access the application.

 * When the user access the application by a web browser, 
   the application server can send a JAVA viewer applet. 
   Therefore, the user doesn't need to install any plugins. 

 * Even if the application uses some Tcl/Tk extensions, 
   the user doesn't need to install such extensions.

 * No need to send the source of the application.
   Funcamentally, interchanged data are only window events 
   and display images on RFB protocol. 
   So, it can decrease the cost of care for security.

Disadvantages are, 

 * Slow, because it is remote access through networks.

 * Much resources are required on the server.

A sample script based on this concept is attached 
at the end of this mail.
That requires 2005/06/08 or later version of Ruby.
Please note that, because of using multi-tk library, 
the description of script on the slave interpreter is 
almost same as the description of usual Ruby/Tk script.

Although it is an old and slow machine and is not 
decided how long term it is available to use, 
there is a server to try the sample script.
Please access 131.206.154.81:33 by VNC viewer, 
or http://131.206.154.81/ by web browser. 
Of course, Ruby is not required on user's machine.

# When by a web browser, a dialog to require password is shown. 
# Please ignore it and press the "OK" button with empty password. 

Any comments (or wish to participate in the development ;-))
are welcome.

                      Hidetoshi NAGAI (nagai / ai.kyutech.ac.jp)

---------------------------------------------------------------
The following is the sample script.
===============================================================
#!/usr/bin/env ruby
#
#  Ruby/Tk+VNC :: concept example
#
#                             by  Hidetoshi NAGAI (nagai / ai.kyutech.ac.jp)
#
require 'multi-tk'
require 'singleton'

class VNC_Wall < TkCanvas
  include Singleton

  def initialize
    @new_relx = 0.4
    @new_rely = 0.4

    super(:width=>TkWinfo.screenwidth('.'), 
          :height=>TkWinfo.screenheight('.'))
    self.pack
  end

  ######################################

  class Window_Frame < TkcWindow
    def _title_bind
      @titlebar.bind('ButtonPress-1', 
                     proc{|rx, ry| 
                       @rx = rx; @ry = ry; @sx, @sy = self.coords
                       @base.raise
                     }, '%X', '%Y')

      @titlebar.bind('B1-Motion', 
                     proc{|rx, ry|
                       wx = @wall.winfo_rootx; wy = @wall.winfo_rooty
                       if rx > wx && rx < wx + @wall.width &&
                           ry > wy && ry < wy + @wall.height
                         self.coords = [@sx + (rx - @rx), @sy + (ry - @ry)]
                       end
                     }, '%X', '%Y')
    end
    private :_title_bind

    def initialize(wall, title, coords, keys={})
      @wall = wall
      @base = TkFrame.new(@wall, :borderwidth=>3, :relief=>:ridge)
      @titlebar = TkLabel.new(@base, :text=>" #{title} ", :relief=>:raised, 
                              :foreground=>'white', 
                              :background=>'midnight blue').pack(:fill=>:x)
      @container = TkFrame.new(@base, :container=>true).pack(:fill=>:both, 
                                                             :expand=>true)
      super(@wall, coords[0], coords[1], keys)
      self.window = @base
      _title_bind
    end

    def winid
      @container.winfo_id
    end

    def title(txt)
      @titlebar.text(txt)
    end
  end

  ######################################

  def new_window(title, coords, keys={})
    keys = _symbolkey2str(keys)
    keys['anchor'] = 'nw'
    Window_Frame.new(self, title, coords, keys)
  end

  def new_root(keys={})
    keys = _symbolkey2str(keys)
    coords = keys.delete('coords') || 
      [self.width * @new_relx, self.height * @new_rely]
    title  = keys.delete('title')  || 'root'
    new_window(title, coords, keys)
  end
  private :new_root

  ######################################

  class TOPLEVEL_ARG < Exception
    alias value message
  end

  ######################################

  def new_toplevel(slave_ip, top, 
                   coords=[self.width * @new_relx, self.height * @new_rely])
    w = new_window("toplevel(#{top})", coords)
    MultiTkIp.invoke_hidden(slave_ip, 'toplevel', top, '-use', w.winid)
  end

  def replace_toplevel_cmd(slave_ip)
    th = Thread.new(slave_ip){|ip|
      begin
        Thread.stop
      rescue TOPLEVEL_ARG => arg
        begin
          new_toplevel(ip, arg.value)
        rescue Exception => e
        end
        retry
      end
    }

    cmd = TkComm._get_eval_string(proc{|t|
                                    th.raise(TOPLEVEL_ARG.new(t))
                                    until slave_ip.eval_proc{TkWinfo.exist?(t)}
                                      Thread.pass
                                      Tk.update
                                    end
                                  })
    slave_ip._eval("proc toplevel {path} {#{cmd} $path}")
  end

  ######################################

  def new_slave(safe=nil, keys={}, &b)
    if safe.kind_of?(Hash)
      keys = _symbolkey2str(safe)
      safe = keys.delete('safe')
    end
    w = new_root(keys)
    ip = MultiTkIp.new_trusted_slave(safe, {:use=>w.winid}, &b)
    MultiTkIp.hide_cmd(ip, 'toplevel')
    replace_toplevel_cmd(ip)
    ip
  end
  alias new_trusted_slave new_slave

  def new_safe_slave(safe=4, keys={}, &b)
    if safe.kind_of?(Hash)
      keys = _symbolkey2str(safe)
      safe = keys.delete('safe') || 4
    end
    w = new_root(keys)
    ip = MultiTkIp.new_safe_slave(safe, {:use=>w.winid}, &b)
    replace_toplevel_cmd(ip)
    ip
  end
  alias new_safeTk new_safe_slave
end

#===============================================================

if $0 == __FILE__
  timeout = 60

  wall = VNC_Wall.instance
  wall[:background] = 'skyblue'

  #-----------------------------------------------------------

  TkcText.new(wall, 150, 50, :fill=>'navyblue', :font=>'courier -14', 
              :text=>"Ruby/Tk+VNC :: concept example")
  TkcText.new(wall, 370, 170, :text=><<EOT)
The root window of the safeTk IP is 
embedded in Master IP's frame widget.

You can move the root window by 
'Button-1 + Motion' on the titlebar.
Master IP works like as a window manager.

No window manager on the VNC server.
Running one Ruby/Tk process only.
EOT
#'
  TkcText.new(wall, 150, 300, 
              :text=>"This example will exit in #{timeout} seconds.")
  TkcText.new(wall, 350, 350, 
              :text=>"This is Master IP's canvas widget.\n\t($SAFE==#{$SAFE})")

  #-----------------------------------------------------------

  # ip = wall.new_trusted_slave(:title=>'slave root', :coords=>[50, 70]){
  # ip = wall.new_safeTk(3, :title=>'slave root', :coords=>[50, 70]){
  ip = wall.new_safeTk(:title=>'slave root', :coords=>[50, 70]){
    TkLabel.new(:text=>"safeTk interpreter's root").pack(:padx=>10, :pady=>5)

    top = nil
    cnt = 0

    b1 = TkButton.new(:text=>'create Toplevel')

    b2 = TkButton.new(:text=>'add label to the toplevel', :state=>:disabled, 
                      :command=>proc{
                        cnt += 1
                        TkLabel.new(top, 
                                    :text=>"Pressed(#{cnt})!! $SAFE=#{$SAFE}"
                                    ).pack
                      })

    b1.command = proc{
      top = TkToplevel.new
      b1[:state] = :disabled
      b2[:state] = :active
      TkLabel.new(top, 
                  :text=>'New toplevel of slaveIP').pack(:padx=>20, :pady=>30)
    }

    label = TkLabel.new(:foreground=>'red', :text=>"\n")
    timer = TkTimer.new(500, 1, proc{label.text = "\n"})
    TkButton.new(:text=>'BUTTON', 
                 :command=>proc{
                   timer.cancel
                   label.text = "button is pressed!!\n($SAFE==#{$SAFE})"
                   timer.start
                 }).pack(:padx=>5, :pady=>5, :fill=>:x)
    label.pack

    Tk.pack(b1, b2, :fill=>:x, :padx=>5, :pady=>5)
  }

  #-----------------------------------------------------------

  Tk.after(timeout * 1000){exit}
  Tk.mainloop
end