File Xvnc.pl of Package xorg-x11-Xvnc

#!/usr/bin/perl

# Emulate Xvnc by starting x11vnc inside Xvfb
# (c) 2011 Matthias Hopf <mhopf@suse.de>
# For a number of additional pitfalls see http://www.karlrunge.com/x11vnc/faq.html#faq-xvfb

# TODO:
# - authorization with -query
# - choose a fresh display with -inetd

use POSIX();
use POSIX ":sys_wait_h";
use IO::Handle;

#
# Option parsing
#


@cmd_x11 = ("/usr/bin/Xvfb");
@cmd_vnc = ("/usr/bin/x11vnc");
$display  = "";
$geometry = "800x600";
$depth    = 24;
$rfbport  = "";
$httpdir  = "";
$auth     = "";
$inetd    = 0;

# Preparse options for -inetd (logfile generation)
for $_ (@ARGV) {
    $inetd = 1 if $_ eq "-inetd";
}

# Logfile generation for -inetd
if ($inetd) {
    $logfile = $ENV{HOME}."/.Xvnc.log";
    $logfile = "/var/log/Xvnc.log" if $> == 0;
    unlink "$logfile.old";
    rename $logfile, "$logfile.old";
    close STDERR;
    unless (open STDERR, '>', $logfile) {
	chomp ($logfile=`mktemp /tmp/Xvnc.XXXXXXXXXX`);
	unless (open STDERR, '>', $logfile) {
	    unless (open STDERR, '>', "/dev/null") {
		die;
	    }
	}
    }
    STDERR->autoflush(1);
    chmod 0600, $logfile;
    STDOUT->autoflush(1);
}

while ($_ = shift @ARGV) {
    # Special handling
    if      (/^:/) {
	$display = $_;
    } elsif ($_ eq "-auth") {
	$auth = shift @ARGV;
    } elsif ($_ eq "-geometry") {
	$geometry = shift @ARGV;
    } elsif ($_ eq "-depth") {
	$depth = shift @ARGV;
    } elsif ($_ eq "-rfbport") {
	$rfbport = shift @ARGV;
    } elsif ($_ eq "-httpd") {
	$httpdir = shift @ARGV;
    } elsif ($_ eq "-inetd") {
    }
    # Pure x11vnc options
    elsif   ($_ eq "-loginauth") {
	push @cmd_vnc, "-unixpw";
    } elsif ($_ eq "-rfbwait") {
	push @cmd_vnc, "-timeout", ((shift @ARGV) + 999) / 1000;
    } elsif ($_ eq "-interface") {
	push @cmd_vnc, "-listen", shift @ARGV;
    }
    # Direct pure x11vnc options
    elsif   (/^(-viewonly|-localhost|-nocursor|-alwaysshared|-nevershared|-dontdisconnect)$/) {
	push @cmd_vnc, $_;
    }
    # Direct pure x11vnc options with 1 argument
    elsif   (/^(-desktop|-rfbauth|-deferupdate|-httpport)$/) {
	push @cmd_vnc, $_, shift @ARGV;
    }
    # Direct pure Xserver options
    elsif   (/^(-ac|-br|\+bs|-bs|-c|-core|-dpms|-I|-nolock|-logo|nologo|-noreset|-nr|-reset|-pn|-nopn|-r|r|ttyxx|v|-tst|-v|-wm|-wr|-maxbigreqsize|\+xinerama|-xinerama|-dumbSched|-retro|-terminate|-broadcast|-once|-ardelay|-arinterval)$/) {
	push @cmd_x11, $_;
    }
    # Direct pure Xserver options with 1 argument
    elsif   (/^(-a|-audit|c|-cc|-dpi|-deferglyphs|-f|-fc|-fn|-fp|-ld|-lf|-ls|-nolisten|-p|-s|-t|-to|-schedInterval|\+extension|-extension|-query|-indirect|-port|-from|-class|-cookie|-displayID)$/) {
	push @cmd_x11, $_, shift @ARGV;
    }
    # Ignore following options for now
    elsif   (/^(-economictranslate|-lazytight)$/) {
    }
    # Ignore following options for now with 1 argument
    elsif   (/^(-pixelformat)$/) {
	shift;
    }
    # Currently unsupported
    elsif   (/^(-render|-multicast|[+-]accessx)$/) {
	# -render [default|mono|gray|color] set render color alloc policy
	#   There is -render <arg> of Xvnc  *and* -render of Xvfb w/o arg...
	# -multicast [addr [hops]] IPv6 multicast for XDMCP
	#   Difficult option parsing, no use with Xvnc
	# [+-]accessx [ timeout [ timeout_mask [ feedback [ options_mask] ] ] ]
	#                       enable/disable accessx key sequences
	#   Difficult option parsing, gdm overwrites opts anyway
	# -udpinputport port     UDP port for keyboard/pointer data
	#   Unknown how to support
	print STDERR "Xvnc option $_ currently unsupported!\n";
	exit 1;
    }
    else {
	print STDERR <<"EOHELP";
use: Xvnc [:<display>] [option]
-a #                   default pointer acceleration (factor)
-ac                    disable access control restrictions
-audit int             set audit trail level
-auth file             select authorization file
-br                    create root window with black background
+bs                    enable any backing store support
-bs                    disable any backing store support
-c                     turns off key-click
c #                    key-click volume (0-100)
-cc int                default color visual class
-nocursor              disable the cursor
-core                  generate core dump on fatal error
-dpi int               screen resolution in dots per inch
-dpms                  disables VESA DPMS monitor control
-deferglyphs [none|all|16] defer loading of [no|all|16-bit] glyphs
-f #                   bell base (0-100)
-fc string             cursor font
-fn string             default font name
-fp string             default font path
-help                  prints message with these options
-I                     ignore all remaining arguments
-ld int                limit data space to N Kb
-lf int                limit number of open files to N
-ls int                limit stack space to N Kb
-nolock                disable the locking mechanism
-logo                  enable logo in screen saver
nologo                 disable logo in screen saver
-nolisten string       don't listen on protocol
-noreset               don't reset after last client exists
-nr                    create root window with no background
-reset                 reset after last client exists
-p #                   screen-saver pattern duration (minutes)
-pn                    accept failure to listen on all ports
-nopn                  reject failure to listen on all ports
-r                     turns off auto-repeat
r                      turns on auto-repeat
-render [default|mono|gray|color] set render color alloc policy
-retro                 start with classic stipple and cursor
-s #                   screen-saver timeout (minutes)
-t #                   default pointer threshold (pixels/t)
-terminate             terminate at server reset
-to #                  connection time out
-tst                   disable testing extensions
ttyxx                  server started from init on /dev/ttyxx
v                      video blanking for screen-saver
-v                     screen-saver without video blanking
-wm                    WhenMapped default backing-store
-wr                    create root window with white background
-maxbigreqsize         set maximal bigrequest size
+xinerama              Enable XINERAMA extension
-xinerama              Disable XINERAMA extension
-dumbSched             Disable smart scheduling, enable old behavior
-schedInterval int     Set scheduler interval in msec
+extension name        Enable extension
-extension name        Disable extension
-query host-name       contact named host for XDMCP
-broadcast             broadcast for XDMCP
-multicast [addr [hops]] IPv6 multicast for XDMCP
-indirect host-name    contact named host for indirect XDMCP
-port port-num         UDP port number to send messages to
-from local-address    specify the local address to connect from
-once                  Terminate server after one session
-class display-class   specify display class to send in manage
-cookie xdm-auth-bits  specify the magic cookie for XDMCP
-displayID display-id  manufacturer display ID for request
[+-]accessx [ timeout [ timeout_mask [ feedback [ options_mask] ] ] ]
                       enable/disable accessx key sequences
-ardelay               set XKB autorepeat delay
-arinterval            set XKB autorepeat interval
-geometry WxH          set framebuffer width & height
-depth D               set framebuffer depth
-pixelformat format    set pixel format (BGRnnn or RGBnnn)
-udpinputport port     UDP port for keyboard/pointer data
-rfbport port          TCP port for RFB protocol
-rfbwait time          max time in ms to wait for RFB client
-nocursor              don't put up a cursor
-rfbauth passwd-file   use authentication on RFB protocol
-loginauth             use login-style Unix authentication
-httpd dir             serve files via HTTP from here
-httpport port         port for HTTP
-deferupdate time      time in ms to defer updates (default 40)
-economictranslate     less memory-hungry translation
-lazytight             disable "gradient" filter in tight encoding
-desktop name          VNC desktop name (default x11)
-alwaysshared          always treat new clients as shared
-nevershared           never treat new clients as shared
-dontdisconnect        don't disconnect existing clients when a new non-shared
                       connection comes in (refuse new connection instead)
-localhost             only allow connections from localhost
                           to the vnc ports. Use -nolisten tcp to disable
                           remote X clients as well.
-viewonly              let clients only view the desktop
-interface ipaddr      only bind to specified interface address
-inetd                 Xvnc is launched by inetd
EOHELP
	exit 1;
    }
}

#
# Command line generation
#

push @cmd_x11, "-screen", "0", "$geometry"."x$depth";
push @cmd_x11, "-cc", "4" if $depth > 8;
if ($inetd) {
    push @cmd_vnc, "-inetd" if $inetd;
} else {
    push @cmd_vnc, $rfbport eq "" ? ("-N") : ("-rfbport", $rfbport);
    push @cmd_vnc, $httpdir eq "" ? ("-http") : ("-httpdir", $httpdir);
    # -threads doesn't work with -inetd?!?
    push @cmd_vnc, "-shared", "-threads";
}
if ($auth ne "") {
    push @cmd_x11, "-auth", $auth;
    push @cmd_vnc, "-auth", $auth;
}

push @cmd_vnc, "-norc", "-forever";
push @cmd_vnc, "-cursor_drag", "-scrollcopyrect", "always", "-nowireframe";
#-env FD_XDM=1

# Multi-keyboard corrections ?!?
push @cmd_vnc, "-skip_lockkeys", "-add_keysyms", "-clear_all";

push @cmd_x11, $display;
push @cmd_vnc, "-display", $display;

#
# Process startup preparation
#

$ret     = 0;
$pid_x11 = 0;
$pid_vnc = 0;
$pid_pip = 0;
$got_usr1= 0;
$got_usr2= 0;

$orig_usr1 = $SIG{USR1};

# Wait for any process, and check for known processes
# Args: nonblock(1)
sub waitall {
    my $id;
    if ($_[0]) {
	$id = waitpid (-1, WNOHANG);
    } else {
	$id = wait ();
    }
    if ($id > 0 && $ret == 0 && $? != 0) {
	$ret = ($? >> 8);
	$ret = 255 if $? < 256;
    }
    $pid_x11 = 0 if $id == $pid_x11;
    $pid_vnc = 0 if $id == $pid_vnc;
    $pid_pip = 0 if $id == $pid_pip;
    return $id;
}

# Terminate all subprocesses
sub terminate {
    # Wait for all processes to actually end by themselves
    eval {
	alarm 2;
	while ($pid_x11 > 0 || $pid_vnc > 0 || $pid_pip > 0) {
	    last if waitall (0) < 0;
	}
	alarm 0;
    };

    # Terminate both main processes
    kill QUIT, $pid_x11 if $pid_x11 > 0;
    kill QUIT, $pid_vnc if $pid_vnc > 0;

    # Wait for both processes to actually end
    eval {
	alarm 5;
	while ($pid_x11 > 0 || $pid_vnc > 0 || $pid_pip > 0) {
	    last if waitall (0) < 0;
	}
	alarm 0;
    };

    # Kill leftover processes
    kill KILL, $pid_x11 if $pid_x11 > 0;
    kill KILL, $pid_vnc if $pid_vnc > 0;
    kill KILL, $pid_pip if $pid_pip > 0;
}

sub END {
    terminate();
}

# Signal handler
sub sig_exit {
    terminate();
    $SIG{$_[0]} = "DEFAULT";
    kill $_[0], $$;
}

sub sig_alrm {
    $SIG{ALRM} = \&sig_alrm;
    die;
}

sub sig_usr1 {
    $SIG{USR1} = \&sig_usr1;
    $got_usr1++;
}

sub sig_usr2 {
    $SIG{USR2} = \&sig_usr2;
    $got_usr2++;
}

# Set signal handlers for cleanup
for $s (HUP, INT, QUIT, TERM) {
    $SIG{$s} = \&sig_exit;
}
$SIG{ALRM} = \&sig_alrm;
$SIG{PIPE} = "IGNORE";
# Set signal handlers for IPC
$SIG{USR1} = \&sig_usr1;
$SIG{USR2} = \&sig_usr2;

#
# Process startup
#

eval {
    alarm 30;

    # Start Xserver
    print STDERR "* Starting up Xserver\n* ".join (" ", @cmd_x11)."\n";
    $pid_x11 = fork();
    die "fork failed: $!" unless defined $pid_x11;
    if (! $pid_x11) {
	# Redirect stdout into stderr (for handling inetd)
	close STDOUT;
	open STDOUT, ">&STDERR";
	close STDIN;
	# Ignore USR1 to indicate Xserver to ping us when ready
	$SIG{USR1}="IGNORE";
	#exec "/usr/bin/Xvfb", "-screen", "0", "800x600x24", ":4";
	exec @cmd_x11;
	die "Starting Xvfb";
    }

    # Wait for Xserver to either finish startup, or being killed
    my $retries=10;
    while ($retries > 0 && !$got_usr1) {
	last if waitall (1) > 0;
	sleep 1;
	$retries--;
    }
    exit $ret if !$pid_x11;
    exit 1    if !$got_usr1;

    # Create stdout pipe for vnc server
    pipe R, W;

    # Start vnc server
    print STDERR "* Starting up vnc server\n* ".join (" ", @cmd_vnc)."\n";
    $pid_vnc = fork();
    die "fork failed: $!" unless defined $pid_vnc;
    if (! $pid_vnc) {
	# Don't terminate X server inside child if dying here
	$pid_x11=0;
	# Redirect stderr into pipe
	close R;
	close STDERR;
	open STDERR, ">&W";
	close W;
	#exec "/usr/bin/x11vnc", "-N", "-display", ":4";
	exec @cmd_vnc;
	die "Starting x11vnc";
    }
    close W;

    # Start process for parsing vnc output
    $pid_pip = fork();
    die "fork failed: $!" unless defined $pid_pip;
    if (! $pid_pip) {
	close STDIN;
	close STDOUT;
	while (<R>) {
	    print STDERR "vnc: $_";
	    kill USR2, getppid() if /^[0-9\/: ]*screen setup finished\.\s*$/;
	}
	exit 0;
    }
    close R;

    # Wait for vnc server being ready
    my $retries=10;
    while ($retries > 0 && !$got_usr2) {
	last if waitall (1) > 0;
	sleep 1;
	$retries--;
    }
    exit $ret if !$pid_vnc;
    exit 1    if !$got_usr2;

    alarm 0;
};

print STDERR "* All up and running\n";
# Ping parent if it wants to get informed about us being ready
kill USR1, getppid() if $orig_usr1 eq "IGNORE";

#
# Wait for shutdown
#

# Wait for one of the processes to terminate
while ($pid_x11 > 0 && $pid_vnc > 0 && $pid_pip > 0) {
    last if waitall (0) < 0;
}

exit $ret;

openSUSE Build Service is sponsored by