# Simple VNC viewer using the tkvnc widget from http://www.ch-werner.de/tkvnc
#############################################################################
#
# To scroll the VNC widget use three finger wipe. To zoom the VNC widget
# use two finger pinch gesture. To bring up the on-screen keyboard tap
# with two fingers in the area where you want to type in.
#
# The "Back" key brings up the option dialog with entries for host,
# password etc. and buttons to exit and connect/disconnect.
package require vnc
wm attributes . -fullscreen 1
. configure -bg black
sdltk touchtranslate 13 ;# RMB, pan/zoom, fingers translated
catch {borg screenorientation landscape}
array set VNCEV {fbits 0 trigger 0}
array set VNCSET {host {} pwd {} shared 0 viewonly 0 lifecycle 0}
array set VNC {host {} lastconnect 1}
# callback on connection status change from VNC widget
proc vnc_info {data} {
global VNCSET VNC
if {$VNCSET(lifecycle)} {
return
}
array set VNC $data
if {$VNC(lastconnect) && !$VNC(connected)} {
vnc_settings
} elseif {!$VNC(lastconnect) && $VNC(connected)} {
if {[catch {sdltk root $VNC(width) $VNC(height)}]} {
.vnc disconnect
return
}
set VNCSET(host) $VNC(host)
set VNCSET(shared) $VNC(shared)
set VNCSET(viewonly) $VNC(viewonly)
if {![catch {open ~/vnc.set w} f]} {
puts $f [array get VNCSET]
close $f
}
}
set VNC(lastconnect) $VNC(connected)
}
# settings dialog
proc vnc_settings {} {
global VNCSET VNC
set w .settings
if {[winfo exists $w]} {
if {$VNC(connected)} {
$w.buttons.a configure -text "Disconnect" \
-command [list vnc_command $w disc]
} else {
$w.buttons.a configure -text "Connect" \
-command [list vnc_command $w conn]
}
return
}
toplevel $w
wm title $w "VNC Options"
wm protocol $w WM_DELETE_WINDOW [list $w.buttons.c invoke]
wm transient $w .
ttk::label $w.lh -text "Host:"
grid $w.lh -row 0 -column 0 -sticky e -padx 5 -pady 5
ttk::entry $w.host -textvariable VNCSET(host)
grid $w.host -row 0 -column 1 -sticky ew -padx 5 -pady 5
ttk::label $w.lp -text "Password:"
grid $w.lp -row 1 -column 0 -sticky e -padx 5 -pady 5
ttk::entry $w.pwd -textvariable VNCSET(pwd) -show "*"
grid $w.pwd -row 1 -column 1 -sticky ew -padx 5 -pady 5
ttk::checkbutton $w.shared -variable VNCSET(shared) -text "Shared"
grid $w.shared -row 2 -column 1 -sticky w -padx 5 -pady 5
ttk::checkbutton $w.vonly -variable VNCSET(viewonly) -text "View Only"
grid $w.vonly -row 3 -column 1 -sticky w -padx 5 -pady 5
ttk::frame $w.buttons
grid $w.buttons -row 4 -column 0 -columnspan 2 -sticky ew -padx 5 -pady 5
if {$VNC(connected)} {
ttk::button $w.buttons.a -text "Disconnect" -width 12 \
-command [list vnc_command $w disc]
} else {
if {![catch {open ~/vnc.set} f]} {
catch {array set VNCSET [read $f 1024]}
close $f
}
ttk::button $w.buttons.a -text "Connect" -width 12 \
-command [list vnc_command $w conn]
}
ttk::button $w.buttons.x -text "Exit" -width 12 -command {exit 0}
ttk::button $w.buttons.c -text "Cancel" -width 12 \
-command [subst {
grab release $w
focus .vnc
destroy $w
}]
pack $w.buttons.a $w.buttons.x $w.buttons.c -side left -expand 1 -padx 5
bind $w <Break> [list $w.buttons.c invoke]
::tk::PlaceWindow $w
grab $w
}
# perform connect to VNC server
proc vnc_connect {} {
global VNCSET
set args $VNCSET(host)
lappend args $VNCSET(pwd)
if {$VNCSET(shared)} {
lappend args "-shared"
}
if {$VNCSET(viewonly)} {
lappend args "-viewonly"
}
if {[catch {.vnc connect {*}$args} err]} {
tk_messageBox -title "Error" -message $err \
-type ok -icon error
after idle vnc_settings
}
}
# execute connect or disconnect to/from VNC server
proc vnc_command {w what} {
switch -glob -- $what {
disc* {
after idle {
catch {.vnc disconnect}
}
}
conn* {
after idle vnc_connect
}
}
grab release $w
focus .vnc
destroy $w
}
# handle finger events and viewport changes
proc vnc_vp_finger {w op rootx rooty dx dy state} {
global VNCEV
switch $op {
v {
# viewport changed, withdraw trigger
set VNCEV(trigger) 0
}
d {
set VNCEV(x$state) \
[expr round(($rootx * [winfo screenwidth .]) / 10000)]
set VNCEV(y$state) \
[expr round(($rooty * [winfo screenheight .]) / 10000)]
set VNCEV(fbits) [expr $VNCEV(fbits) | (1 << $state)]
if {($VNCEV(fbits) & 6) == 6} {
set VNCEV(trigger) 1
}
}
m {
if {($dx < -15) || ($dx > 15) || ($dy < -15) || ($dy > 15)} {
# motion delta, withdraw trigger
set VNCEV(trigger) 0
}
}
u {
set VNCEV(x$state) \
[expr round(($rootx * [winfo screenwidth .]) / 10000)]
set VNCEV(y$state) \
[expr round(($rooty * [winfo screenheight .]) / 10000)]
set VNCEV(fbits) [expr $VNCEV(fbits) & ~(1 << $state)]
set doit 0
if {$VNCEV(fbits) == 0} {
if {$VNCEV(trigger)} {
set doit 1
}
set VNCEV(trigger) 0
}
if {$doit} {
set dx [expr {$VNCEV(x2) - $VNCEV(x1)}]
set dy [expr {$VNCEV(y2) - $VNCEV(y1)}]
set px [expr {$VNCEV(x1) + $dx / 2}]
set py [expr {$VNCEV(y1) + $dy / 2}]
sdltk textinput 1 $px $py
}
}
}
}
# handle app lifecycle events
proc vnc_lifecycle {suspend} {
global VNCSET VNCEV
if {$suspend} {
set VNCEV(fbits) 0
set VNCEV(trigger) 0
set VNCSET(lastinfo) [.vnc info]
set VNCSET(lifecycle) 1
catch {.vnc disconnect}
} else {
set VNCSET(lifecycle) 0
array set info $VNCSET(lastinfo)
if {$info(connected)} {
set args $info(host)
lappend args $VNCSET(pwd)
if {$info(shared)} {
lappend args "-shared"
}
if {$info(viewonly)} {
lappend args "-viewonly"
}
if {[catch {.vnc connect {*}$args} err]} {
after idle vnc_settings
}
}
}
}
vnc .vnc -bd 0 -relief flat -highlightthickness 0 -bg black \
-infocommand vnc_info
pack .vnc -side top -fill both -expand 1
focus .vnc
wm withdraw .
bind . <<WillEnterBackground>> {vnc_lifecycle 1}
bind . <<WillEnterForeground>> {vnc_lifecycle 0}
bind . <<ViewportUpdate>> {vnc_vp_finger %W v %x %y %X %Y %s}
bind . <<FingerDown>> {vnc_vp_finger %W d %x %y %X %Y %s}
bind . <<FingerMotion>> {vnc_vp_finger %W m %x %y %X %Y %s}
bind . <<FingerUp>> {vnc_vp_finger %W u %x %y %X %Y %s}
bind VNC <Control-Tab> {}
bind VNC <Break> {vnc_settings ; break}
# initially bring up settings dialog
after idle {
wm deiconify .
event generate .vnc <Key-Break>
}