Artifact [8c4639b2ae]
Not logged in

Artifact 8c4639b2aea261b4e8e3f865fb30fd63af97c439:


# 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
}