From: Hidetoshi NAGAI Date: 2005-06-10T12:43:28+09:00 Subject: [RFC] framework of Ruby/Tk + VNC 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=><"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