# Simple VNC viewer using the tkvnc widget from http://www.ch-werner.de/tkvnc
#############################################################################
#
# Android:
#
# 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
if {[info command sdltk] eq "sdltk"} {
wm attributes . -fullscreen 1
} else {
wm geometry . 640x480
}
. configure -bg black
if {[info command borg] eq "borg"} {
# on Android
sdltk touchtranslate 13 ;# RMB, pan/zoom, fingers translated
borg screenorientation landscape
set ::VNC(android) 1
} else {
if {[info command sdltk] eq "sdltk"} {
if {[sdltk maxroot] eq {0 0}} {
sdltk touchtranslate 0
}
if {[sdltk framebuffer]} {
package require touchcal
}
}
set ::VNC(android) 0
}
array set ::VNCEV {fbits 0 trigger 0}
array set ::VNCSET {host {} pwd {} shared 0 viewonly 0 auto 0 lifecycle 0}
array set ::VNC {host {} auto 0 lastconnect 1 connected 0}
# callback on connection status change from VNC widget
proc vnc_info {data} {
if {$::VNCSET(lifecycle)} {
return
}
array set ::VNC $data
if {$::VNC(lastconnect) && !$::VNC(connected)} {
vnc_settings
} elseif {!$::VNC(lastconnect) && $::VNC(connected)} {
if {[info command sdltk] eq "sdltk"} {
set sw [winfo screenwidth .]
set sh [winfo screenheight .]
if {[sdltk maxroot] eq {0 0}} {
set dosb 1
} elseif {[catch {sdltk root $::VNC(width) $::VNC(height)}]} {
set dosb 1
} else {
set dosb 0
}
if {$dosb} {
if {$::VNC(width) <= $sw && $::VNC(height) <= $sh} {
set dosb 0
}
}
if {$dosb} {
if {$::VNC(width) > $sw - 20} {
if {![winfo exists .x]} {
ttk::scrollbar .x -orient horizontal -takefocus 0 \
-command {.vnc xview}
grid .x -row 1 -column 0 -sticky ew
.vnc configure -xscrollcommand {.x set}
}
incr dosb
} elseif {[winfo exists .x]} {
.vnc configure -xscrollcommand {}
destroy .x
}
if {$::VNC(height) > $sh - 20} {
if {![winfo exists .y]} {
ttk::scrollbar .y -orient vertical -takefocus 0 \
-command {.vnc yview}
grid .y -row 0 -column 1 -sticky ns
.vnc configure -yscrollcommand {.y set}
}
incr dosb
} elseif {[winfo exists .y]} {
.vnc configure -yscrollcommand {}
destroy .y
}
if {$dosb > 2} {
if {![winfo exists .f]} {
frame .f
grid .f -row 1 -column 1 -sticky nsew
}
} elseif {[winfo exists .f]} {
destroy .f
}
} else {
if {[winfo exists .f]} {
destroy .f
}
if {[winfo exists .x]} {
.vnc configure -xscrollcommand {}
destroy .x
}
if {[winfo exists .y]} {
.vnc configure -yscrollcommand {}
destroy .y
}
}
} else {
wm geometry . "$::VNC(width)x$::VNC(height)"
}
set ::VNCSET(host) $::VNC(host)
set ::VNCSET(shared) $::VNC(shared)
set ::VNCSET(viewonly) $::VNC(viewonly)
set ::VNCSET(auto) $::VNC(auto)
if {![catch {open ~/vnc.set w} f]} {
catch {set ::VNCSET(pwd) [binary encode base64 $::VNCSET(pwd)]}
puts $f [array get ::VNCSET]
close $f
catch {set ::VNCSET(pwd) [binary decode base64 $::VNCSET(pwd)]}
}
}
set ::VNC(lastconnect) $::VNC(connected)
}
# settings dialog
proc vnc_settings {} {
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::frame $w.f
ttk::label $w.lh -text "Host:"
grid $w.lh -in $w.f -row 0 -column 0 -sticky e -padx 5 -pady 5
ttk::entry $w.host -textvariable ::VNCSET(host)
grid $w.host -in $w.f -row 0 -column 1 -sticky ew -padx 5 -pady 5
ttk::label $w.lp -text "Password:"
grid $w.lp -in $w.f -row 1 -column 0 -sticky e -padx 5 -pady 5
ttk::entry $w.pwd -textvariable ::VNCSET(pwd) -show "*"
grid $w.pwd -in $w.f -row 1 -column 1 -sticky ew -padx 5 -pady 5
ttk::checkbutton $w.shared -variable ::VNCSET(shared) -text "Shared"
grid $w.shared -in $w.f -row 2 -column 1 -sticky w -padx 5 -pady 5
ttk::checkbutton $w.vonly -variable ::VNCSET(viewonly) -text "View Only"
grid $w.vonly -in $w.f -row 3 -column 1 -sticky w -padx 5 -pady 5
ttk::checkbutton $w.auto -variable ::VNCSET(auto) -text "Auto Connect"
grid $w.auto -in $w.f -row 4 -column 1 -sticky w -padx 5 -pady 5
if {[info command sdltk] eq "sdltk" && !$::VNC(android) &&
[sdltk framebuffer]} {
ttk::button $w.tcal -text "Calibrate Touchscreen ..." \
-command [list vnc_touchcal]
grid $w.tcal -in $w.f -row 5 -column 0 -columnspan 2 -padx 5 -pady 5
}
ttk::frame $w.buttons
grid $w.buttons -in $w.f \
-row 6 -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
catch {set ::VNCSET(pwd) [binary decode base64 $::VNCSET(pwd)]}
vnc_touchset
}
set ::VNCSET(auto) $::VNC(auto)
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 {
destroy .vnc
exit 0
}
ttk::button $w.buttons.c -text "Cancel" -width 12 \
-command [subst {
grab release $w
destroy $w
focus .vnc
}]
pack $w.buttons.a $w.buttons.x $w.buttons.c -side left -expand 1 -padx 5
if {$::VNC(android)} {
bind $w <Break> [list $w.buttons.c invoke]
}
pack $w.f -side top -fill both -expand 1
::tk::PlaceWindow $w widget [winfo parent $w]
grab $w
}
# perform touchscreen calibration
proc vnc_touchcal {} {
set data [touchcal::calibrate .tcal]
if {[llength $data]} {
set ::VNCSET(touchcal) $data
if {![catch {open ~/vnc.set w} f]} {
catch {set ::VNCSET(pwd) [binary encode base64 $::VNCSET(pwd)]}
puts $f [array get ::VNCSET]
close $f
catch {set ::VNCSET(pwd) [binary decode base64 $::VNCSET(pwd)]}
}
vnc_touchset
}
}
# commit touchscreen calibration
proc vnc_touchset {} {
if {[info command sdltk] eq "sdltk" && !$::VNC(android) &&
[sdltk framebuffer]} {
if {[info exists ::VNCSET(touchcal)]} {
catch {sdltk touchcalibration {*}$::VNCSET(touchcal)}
}
}
}
# perform connect to VNC server
proc vnc_connect {} {
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
} else {
set ::VNC(auto) $::VNCSET(auto)
}
}
# execute connect or disconnect to/from VNC server
proc vnc_command {w what} {
grab release $w
focus .vnc
tk busy .
destroy $w
update
switch -glob -- $what {
disc* {
after idle {
catch {.vnc disconnect}
}
set ::VNC(auto) $::VNCSET(auto)
}
conn* {
after idle vnc_connect
}
}
tk busy forget .
}
# handle finger events and viewport changes
proc vnc_vp_finger {w op rootx rooty dx dy state} {
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} {
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
if {[info command sdltk] eq "sdltk"} {
grid .vnc -row 0 -column 0 -sticky nsew
grid rowconfigure . 0 -weight 1
grid columnconfigure . 0 -weight 1
} else {
pack .vnc -side top -fill both -expand 1
}
focus .vnc
if {$::VNC(android)} {
wm withdraw .
}
# Android App lifecycle
bind . <<WillEnterBackground>> {vnc_lifecycle 1}
bind . <<WillEnterForeground>> {vnc_lifecycle 0}
# sdltk viewport and touchscreen
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> {}
if {$::VNC(android)} {
# Android back key
bind VNC <Break> {vnc_settings ; break}
}
bind VNC <Control-Shift-F8> break
bind VNC <Control-Alt-F8> break
bind VNC <Control-Meta-F8> break
bind VNC <Control-F8> {
# release control keys
.vnc sendkey 0 0xFFE3
.vnc sendkey 0 0xFFE4
vnc_settings
break
}
# initially bring up settings dialog or connect
apply {
{} {
if {![catch {open ~/vnc.set} f]} {
catch {array set ::VNCSET [read $f 1024]}
close $f
catch {set ::VNCSET(pwd) [binary decode base64 $::VNCSET(pwd)]}
vnc_touchset
}
}
}
if {$::VNC(android)} {
after idle {
wm deiconify .
if {$::VNCSET(host) eq "" || !$::VNCSET(auto)} {
vnc_settings
} else {
set ::VNC(lastconnect) 0
vnc_connect
}
}
} elseif {$::VNCSET(host) eq "" || !$::VNCSET(auto)} {
vnc_settings
} else {
set ::VNC(lastconnect) 0
vnc_connect
}