Patched vncserver to support X Font Server

Jeremy D. Impson jdimpson "at" MailBox.Syr.Edu
Sat, 07 Mar 1998 07:29:42 +0000


Hello!  

I want to say thank you to everyone involved with VNC.  I just discovered
it, and I think it's fantastic!

So, I want to contribute (in my small way).

I had a problem starting the server under Linux (2.0.33).  It boiled down
to some font problem.  (Could not load font 'fixed'.)  Somewhere along
the line I got the impression that Xvnc wasn't handling gzipped font
correctly, but I may be wrong.

I used 'xfs' and the ability of Xvnc to connect to it as a fix.  At the
end of this message is a patched copy og vncserver that adds a new flag
'-xfs <port number>'.  THis when run will run xfs and then Xvnc on the
same (font) port.

I noticed inthe list archive others had similar problem with different
solutions.  Maybe some of you will find this more convenient.

Eventually, I'll come up with another script that will cleanly terminate
Xvnc and xfs, probably after I get back from spring break.

Thanks again!

--Jeremy

--------------------------------------------------------------------------
Jeremy Impson					jdimpson "at" acm.org
http://web.syr.edu/~jdimpson			http://camelot.syr.edu
SysAdmin, Student, Programmer, History Buff, Sleeper, Eater, Breather


#####

#!/usr/local/bin/perl
#
#  Copyright (C) 1997, 1998 Olivetti & Oracle Research Laboratory
#
#  This is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This software is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this software; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
#  USA.
#

#
# vncserver - wrapper script to start an X VNC server.
#

#
# First make sure we're operating in a sane environment.
#

&SanityCheck();

#
# Global variables.  You may want to configure some of these for your site.
#

$vncClasses = "/usr/local/vnc/classes";
$vncUserDir = "$ENV{HOME}/.vnc";
$xauthorityFile = "$ENV{HOME}/.Xauthority";

$defaultXStartup
    = ("#!/bin/sh\n\n".
       "xrdb \$HOME/.Xresources\n".
       "xsetroot -solid grey\n".
       "xterm -geometry 80x24+10+10 -ls -title \"\$VNCDESKTOP Desktop\" &\n".
       "twm &\n");

chop($host = `uname -n`);

&ParseOptions("-geometry",1,"-depth",1,"-pixelformat",1,"-name",1,"-xfs",1,"-help",0);

&Usage() if ($opt{'-help'});

&GetXDisplayDefaults();

&CheckGeometryAndDepth();

if (!$opt{'-name'}) {
    $opt{'-name'} = "X"; # default desktop name
}


# Create the user's vnc directory if necessary.

if (!(-e $vncUserDir)) {
    if (!mkdir($vncUserDir,0755)) {
	die "$prog: Could not create $vncUserDir.\n";
    }
}

$desktopLog = "$vncUserDir/".&removeSlashes($opt{'-name'}).".log";
unlink($desktopLog);

    
# Make sure the user has a password.

($z,$z,$mode) = stat("$vncUserDir/passwd");
if (!(-e "$vncUserDir/passwd") || ($mode & 077)) {
    warn "\nYou will require a password to access your desktops.\n\n";
    system("vncpasswd $vncUserDir/passwd"); 
    if (($? >> 8) != 0) {
	exit 1;
    }
}

# Find display number.

if ((@ARGV > 0) && ($ARGV[0] =~ /^:(\d+)$/)) {
    $displayNumber = $1;
    shift(@ARGV);
    if (!&CheckDisplayNumber($displayNumber)) {
	die "A VNC server is already running as :$displayNumber\n";
    }
} else {
    $displayNumber = &GetDisplayNumber();
}

$vncPort = 5900 + $displayNumber;

        
# Make an X server cookie

srand(time+$$);
$cookie = "";
for (1..16) {
    $cookie .= sprintf("%02x", int(rand(256)));
}
    
system("xauth -f $xauthorityFile add $host:$displayNumber . $cookie");

# start x font server (which understands zipped fonts)

if ($opt{'-xfs'}) {
	system ("xfs -port $opt{'-xfs'} > $vncUserDir/xfs-errs 2>&1 &"); 
	sleep 3;
}

# Now start the X VNC Server

$cmd = "Xvnc :$displayNumber";
$cmd .= " -desktop " . &quotedString($opt{'-name'});
$cmd .= " -httpd $vncClasses";
$cmd .= " -auth $xauthorityFile";
$cmd .= " -geometry $opt{'-geometry'}" if ($opt{'-geometry'});
$cmd .= " -depth $opt{'-depth'}" if ($opt{'-depth'});
$cmd .= " -pixelformat $opt{'-pixelformat'}" if ($opt{'-pixelformat'});
$cmd .= " -fp tcp/localhost:$opt{'-xfs'}" if $opt{'-xfs'};; # added to talk to fontserver
$cmd .= " -rfbwait 120000";
$cmd .= " -rfbauth $vncUserDir/passwd";
$cmd .= " -rfbport $vncPort";


# Add font path and color database stuff here, e.g.:
#
# $cmd .= " -fp /usr/lib/X11/fonts/misc/,/usr/lib/X11/fonts/75dpi/";
# $cmd .= " -co /usr/lib/X11/rgb";
#

foreach $arg (@ARGV) {
    $cmd .= " " . &quotedString($arg);
}
$cmd .= " >> " . &quotedString($desktopLog) . " 2>&1";

system("$cmd &");

sleep(3); # give Xvnc a chance to start up

warn "\nNew '$opt{'-name'}' desktop is $host:$displayNumber\n\n";

# Create the user's xstartup script if necessary.

if (!(-e "$vncUserDir/xstartup")) {
    warn "Creating default startup script $vncUserDir/xstartup\n";
    open(XSTARTUP, ">$vncUserDir/xstartup");
    print XSTARTUP $defaultXStartup;
    close(XSTARTUP);
    chmod 0755, "$vncUserDir/xstartup";
}

# Run the X startup script.

warn "Starting applications specified in $vncUserDir/xstartup\n";
warn "Log file is $desktopLog\n\n";

$ENV{DISPLAY}= "$host:$displayNumber";
$ENV{VNCDESKTOP}= $opt{'-name'};

system("$vncUserDir/xstartup >> " . &quotedString($desktopLog) . " 2>&1 &");

exit;


###############################################################################
#
# CheckGeometryAndDepth simply makes sure that the geometry and depth values
# are sensible.
#

sub CheckGeometryAndDepth
{
    if ($opt{'-geometry'}) {
	if ($opt{'-geometry'} =~ /^(\d+)x(\d+)$/) {
	    $width = $1; $height = $2;

	    if (($width<1) || ($height<1)) {
		die "$prog: geometry $opt{'-geometry'} is invalid\n";
	    }

	    while (($width % 4)!=0) {
		$width = $width + 1;
	    }

	    while (($height % 2)!=0) {
		$height = $height + 1;
	    }

	    $opt{-geometry} = "${width}x$height";
	} else {
	    die "$prog: geometry $opt{'-geometry'} is invalid\n";
	}
    }

    if ($opt{'-depth'}) {
	if (($opt{'-depth'} < "8") || ($opt{'-depth'} > 32)) {
	    die "Depth must be between 8 and 32\n";
	}
    }
}


#
# GetDisplayNumber gets the lowest available display number.  A display number
# n is taken if something is listening on the VNC server port (5900+n) or the
# X server port (6000+n).
#

sub GetDisplayNumber
{
    foreach $n (1..99) {
	if (&CheckDisplayNumber($n)) {
	    return $n;
	}
    }
    
    die "$prog: no free display number on $host.\n";
}


#
# CheckDisplayNumber checks if the given display number is available.  A
# display number n is taken if something is listening on the VNC server port
# (5900+n) or the X server port (6000+n).
#

sub CheckDisplayNumber
{
    local ($n) = @_;

    socket(S, $AF_INET, $SOCK_STREAM, 0) || die "$prog: socket failed: $!\n";
    if (!bind(S, pack('S n x12', $AF_INET, 6000 + $n))) {
	close(S);
	return 0;
    }
    close(S);

    socket(S, $AF_INET, $SOCK_STREAM, 0) || die "$prog: socket failed: $!\n";
    if (!bind(S, pack('S n x12', $AF_INET, 5900 + $n))) {
	close(S);
	return 0;
    }
    close(S);

    return 1;
}


#
# GetXDisplayDefaults uses xdpyinfo to find out the geometry, depth and pixel
# format of the current X display being used.  If successful, it sets the
# options as appropriate so that the X VNC server will use the same settings
# (minus an allowance for window manager decorations on the geometry).  Using
# the same depth and pixel format means that the VNC server won't have to
# translate pixels when the desktop is being viewed on this X display (for
# TrueColor displays anyway).
#

sub GetXDisplayDefaults
{
    local (@lines, @matchlines, $width, $height, $defaultVisualId, $i,
	   $red, $green, $blue);

    $wmDecorationWidth = 4;	# a guess at typical size for window manager
    $wmDecorationHeight = 24;	# decoration size

    return if (!defined($ENV{DISPLAY}));

    @lines = `xdpyinfo 2>/dev/null`;

    return if ($? != 0);

    if (!$opt{'-geometry'}) {
	@matchlines = grep(/dimensions/, @lines);
	if (@matchlines) {
	    ($width, $height) = ($matchlines[0] =~ /(\d+)x(\d+) pixels/);

	    $width -= $wmDecorationWidth;
	    $height -= $wmDecorationHeight;

	    $opt{'-geometry'} = "${width}x$height";
	}
    }

    if (!$opt{'-depth'} && !$opt{'-pixelformat'}) {
	@matchlines = grep(/default visual id/, @lines);
	if (@matchlines) {
	    ($defaultVisualId) = ($matchlines[0] =~ /id:\s+(\S+)/);

	    for ($i = 0; $i < @lines; $i++) {
		if ($lines[$i] =~ /^\s*visual id:\s+$defaultVisualId$/) {
		    if (($lines[$i+1] !~ /TrueColor/) ||
			($lines[$i+2] !~ /depth/) ||
			($lines[$i+4] !~ /red, green, blue masks/))
		    {
			return;
		    }
		    last;
		}
	    }

	    return if ($i >= @lines);

	    ($opt{'-depth'}) = ($lines[$i+2] =~ /depth:\s+(\d+)/);
	    ($red,$green,$blue)
		= ($lines[$i+4]
		   =~ /masks:\s+0x([0-9a-f]+), 0x([0-9a-f]+), 0x([0-9a-f]+)/);

	    $red = hex($red);
	    $green = hex($green);
	    $blue = hex($blue);

	    if ($red > $blue) {
		$red = int(log($red) / log(2)) - int(log($green) / log(2));
		$green = int(log($green) / log(2)) - int(log($blue) / log(2));
		$blue = int(log($blue) / log(2)) + 1;
		$opt{'-pixelformat'} = "rgb$red$green$blue";
	    } else {
		$blue = int(log($blue) / log(2)) - int(log($green) / log(2));
		$green = int(log($green) / log(2)) - int(log($red) / log(2));
		$red = int(log($red) / log(2)) + 1;
		$opt{'-pixelformat'} = "bgr$blue$green$red";
	    }
	}
    }
}


#
# quotedString returns a string which yields the original string when parsed
# by a shell.
#

sub quotedString
{
    local ($in) = @_;

    $in =~ s/\'/\'\"\'\"\'/g;

    return "'$in'";
}


#
# removeSlashes turns slashes into underscores for use as a file name.
#

sub removeSlashes
{
    local ($in) = @_;

    $in =~ s|/|_|g;

    return "$in";
}


#
# Usage
#

sub Usage
{
    die("usage: $prog [:<number>] [-name <desktop-name>] [-depth <depth>]\n".
	"                 [-geometry <width>x<height>]\n".
	"                 [-pixelformat rgbNNN|bgrNNN]\n".
	"		  [-xfs <port number>]".
	"                 <Xvnc-options>...\n");
}


#
# ParseOptions takes a list of possible options and a boolean indicating
# whether the option has a value following, and sets up an associative array
# %opt of the values of the options given on the command line. It removes all
# the arguments it uses from @ARGV and returns them in @optArgs.
#

sub ParseOptions
{
    local (@optval) = @_;
    local ($opt, @opts, %valFollows, @newargs);

    while (@optval) {
	$opt = shift(@optval);
	push(@opts,$opt);
	$valFollows{$opt} = shift(@optval);
    }

    @optArgs = ();
    %opt = ();

    arg: while ($arg = shift(@ARGV)) {
	foreach $opt (@opts) {
	    if ($arg eq $opt) {
		push(@optArgs, $arg);
		if ($valFollows{$opt}) {
		    if (@ARGV == 0) {
			&Usage();
		    }
		    $opt{$opt} = shift(@ARGV);
		    push(@optArgs, $opt{$opt});
		} else {
		    $opt{$opt} = 1;
		}
		next arg;
	    }
	}
	push(@newargs,$arg);
    }

    @ARGV = @newargs;
}


#
# Routine to make sure we're operating in a sane environment.
#

sub SanityCheck
{
    local ($cmd);

    #
    # Get the program name
    #

    ($prog) = ($0 =~ m|([^/]+)$|);

    #
    # Check we have all the commands we'll need on the path.
    #

 cmd:
    foreach $cmd ("uname","xauth","Xvnc","vncpasswd") {
	for (split(/:/,$ENV{PATH})) {
	    if (-x "$_/$cmd") {
		next cmd;
	    }
	}
	die "$prog: couldn't find \"$cmd\" on your PATH.\n";
    }

    #
    # Check the HOME environment variable is set
    #

    if (!defined($ENV{HOME})) {
	die "$prog: The HOME environment variable is not set.\n";
    }

    #
    # Check for socket constants and attempt to define suitable defaults
    #

    chop($os = `uname`);

    eval 'require "sys/socket.ph"';
    if ($@) {
	if ($os eq "SunOS") {
	    $AF_INET = 2;
	    $SOCK_STREAM = 2;
	} else {
	    $AF_INET = 2;
	    $SOCK_STREAM = 1;
	}
    } else {
	$AF_INET = &AF_INET;
	$SOCK_STREAM = &SOCK_STREAM;
    }
}