H
Hidetoshi NAGAI
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 ([email protected])
---------------------------------------------------------------
The following is the sample script.
===============================================================
#!/usr/bin/env ruby
#
# Ruby/Tk+VNC :: concept example
#
# by Hidetoshi NAGAI ([email protected])
#
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,
ady=>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,
ady=>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,
ady=>5, :fill=>:x)
label.pack
Tk.pack(b1, b2, :fill=>:x,
adx=>5,
ady=>5)
}
#-----------------------------------------------------------
Tk.after(timeout * 1000){exit}
Tk.mainloop
end
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 ([email protected])
---------------------------------------------------------------
The following is the sample script.
===============================================================
#!/usr/bin/env ruby
#
# Ruby/Tk+VNC :: concept example
#
# by Hidetoshi NAGAI ([email protected])
#
require 'multi-tk'
require 'singleton'
class VNC_Wall < TkCanvas
include Singleton
def initialize
@new_relx = 0.4
@new_rely = 0.4
super
: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
@container = TkFrame.new(@base, :container=>true).pack
: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
# ip = wall.new_safeTk(3, :title=>'slave root', :coords=>[50, 70]){
ip = wall.new_safeTk
TkLabel.new
top = nil
cnt = 0
b1 = TkButton.new
b2 = TkButton.new
: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
}
label = TkLabel.new
timer = TkTimer.new(500, 1, proc{label.text = "\n"})
TkButton.new
:command=>proc{
timer.cancel
label.text = "button is pressed!!\n($SAFE==#{$SAFE})"
timer.start
}).pack
label.pack
Tk.pack(b1, b2, :fill=>:x,
}
#-----------------------------------------------------------
Tk.after(timeout * 1000){exit}
Tk.mainloop
end