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;