#!/usr/bin/perl

# This is a virtually complete test of all of the protocol's features
# -- it was used by the author during development. It generates a lot
# of output to STDOUT, uses a bunch of memory, and messes with your
# display in various ways. Run it at your own risk.

use X11::Protocol 0.02;

use X11::Keysyms qw(%Keysyms MISCELLANY XKB_KEYS LATIN1);
%Keysyms_name = reverse %Keysyms;

sub pretty
{
    my($x) = @_;
    if (not ref $x)
    {
	if ($x == 0 and $x ne "0")
	{
	    $x = "..." if $x =~ /[\cA-\cZ]/;
	    print "`$x'";
	}
	else
	{
	    printf "$x=0x%x", $x;
	}
    }
    elsif (ref($x) eq "ARRAY")
    {
	my($i);
	print "[";
	for $i (@$x) { pretty($i); print ", ";}
	print "]";
    }
    elsif (ref($x) eq "HASH" or ref($x) eq "X11::Protocol")
    {
	my($k, $v);
	print "{";
	while (($k, $v) = each(%$x))
	{
	    print "$k => ";
	    pretty($v);
	    print ", ";
	}
	print "}";
    }
    else
    {
	print $x;
    }
}

%opts = @ARGV;
$display = $opts{'-d'} || $opts{'-display'} || $ENV{'DISPLAY'} || ":0.0"; 

$x = X11::Protocol->new($display);
pretty $x;
print "\n";
$win = $x->new_rsrc;
print "$win\n";
$x->error_handler(sub {});
$x->error_handler(\&X11::Protocol::default_error_handler);
sub print_event
{
    my(%e) = @_;
    my($i);
    $last_event_time = $e{'time'} if $e{'time'};
    exit if $e{'name'} eq "KeyPress" and ($e{'detail'} == 24 or $done);
    print delete($e{'name'}), ": ";
    print join(", ", map("$_ $e{$_}", keys %e)), "\n";
}
$x->{'event_handler'} = \&print_event;
#$x->{'event_handler'} = 'queue';

$x->req('CreateWindow', $win, $x->{'root'}, "InputOutput",
	$x->{'root_depth'}, "CopyFromParent",
	(0, 0), 100, 100, 1, "backing_store" => "WhenMapped",
	'background_pixel' => $x->{'white_pixel'});
$x->req('ChangeProperty', $win,
	$x->req('InternAtom', "WM_NAME", 0), 
	$x->req('InternAtom', "STRING", 0), 8, "Replace", "Perl X11 Client");  
$x->req('ChangeWindowAttributes', $win, "event_mask" => #0x01ebffff);
	$x->pack_event_mask('KeyPress', 'KeyRelease', 'ButtonPress',
			    'ButtonRelease', 'EnterWindow', 'LeaveWindow',
			    'PointerMotion', 'ButtonMotion', 'KeymapState',
			    'Exposure', 'VisibilityChange', 'StuctureNotify',
			    'SubstructureNotify', 'FocusChange',
			    'PropertyChange', 'ColormapChange'));
print join " ", $x->req('GetWindowAttributes', $win), "\n";
$x->request('MapWindow', $win);
req $x 'ConfigureWindow', $win, "height" => 200, "width" => 200;
$kid1 = $x->new_rsrc;
$x->req('CreateWindow', $kid1, $win, 'InputOutput', $x->{'root_depth'},
	'CopyFromParent', (50, 50), 75, 75, 4);
$kid2 = $x->new_rsrc;
$x->req('CreateWindow', $kid2, $win, 'InputOutput', $x->{'root_depth'},
	'CopyFromParent', (100, 100), 75, 75, 4);
$x->req('MapSubwindows', $win);
sleep 2;
$x->req('CirculateWindow', $win, "LowerHighest");
sleep 2;
$x->req('DestroySubwindows', $win);
print join " ", $x->req('GetGeometry', $win), "\n";
print join " ", 
    $x->req('GetGeometry', $x->{'root'}), "\n";
($root, $parent, @kids) = $x->req('QueryTree', $x->{'root'});
for $kid (@kids) {
    print join " ", $x->req('GetGeometry', $kid), "\n";
}
print $x->req('InternAtom', "WM_NAME", 0), "\n";
for $atom (1 .. 90) {
    print "$atom: ", $x->req('GetAtomName', $atom), ", ";
}
print "\n\n";
for $atom ($x->req('ListProperties', $win)) {
    print $x->atom_name($atom), " => ";
    print join(",", $x->req('GetProperty', $win, $atom, "AnyPropertyType",
			    0, 200, 0)), "\n";
}
$root_wid = $x->{'root'};
for (1 .. 10)
{
    my($e) = $x->pack_event('code' => 2, 'detail' => 25, 'time' => 0,
			    'root' => $root_wid, 'event' => $win, 'child' => 0,
			    'root_x' => 100, 'root_y' => 100, 'event_x' => 5,
			    'event_y' => 5, 'state' => 0, 'same_screen' => 1,
			    'synthetic' => 0);
    $x->req('SendEvent', "PointerWindow", 0, 0, $e);
    $x->req('SendEvent', "PointerWindow", 0, 0,
	    $x->pack_event('name' => "KeyRelease", 'detail' => 25, 'time' => 0,
			   'root' => $root_wid, 'event' => $win, 'child' => 0,
			   'root_x' => 100, 'root_y' => 100, 'event_x' => 5,
			   'event_y' => 5, 'state' => 0, 'same_screen' => 1));
}
print "Grabbing...";
$x->req('GrabPointer', $win, 0, 0, 'Asynchronous', 'Asynchronous', $win, 0, 0);
sleep 2;
$x->req('UngrabPointer', 0);
print "done.\n";
sleep 2;
print "Grabbing server...";
$x->req('GrabServer');
sleep 2;
$x->req('UngrabServer');
print "done.\n";
print "->", join(" ", $x->req('QueryPointer', $win)), "\n";
for $motion ($x->req('GetMotionEvents', $last_event_time, 'CurrentTime', $win))
{
    print "$motion->[0]: ($motion->[1], $motion->[2])\n";
}
print "-->", join(" ", $x->req('TranslateCoordinates',
				      $win => $root_wid, 50, 50)), "\n";
for (1 .. 10)
{
    $x->req('WarpPointer', 'None', $root_wid, 0, 0, 0, 0,
	    rand($x->{'width_in_pixels'} * .9),
	    rand($x->{'height_in_pixels'} * .9));
    sleep 1;
}
print "--->", join(" ", $x->req('GetInputFocus')), "\n";
print "---->", $x->req('QueryKeymap'), "\n";
$fid = $x->new_rsrc;
$x->req('OpenFont', $fid, 'fixed');
print "`fixed' = $fid\n";

%fixed = $x->req('QueryFont', $fid);
print join(" ", %fixed), "\n";
print join(" ", @{$fixed{'min_bounds'}}), "\n";
print join(" ", @{$fixed{'max_bounds'}}), "\n";
%prop = %{$fixed{'properties'}};
foreach $atom (keys %prop)
{
    print $x->atom_name($atom), " => ", $prop{$atom}, "; ";
}
print "\n"; 
foreach $ci (@{$fixed{'char_infos'}})
{
    print join (" ", @$ci), "; ";
}
print "\n";
print join(" ", $x->req('QueryTextExtents', $fid, "\0H\0e\0l\0l\0o")), "\n";
print join("\n", $x->req('ListFonts', '-adobe-*', 50)), "\n";
foreach $font ($x->req('ListFontsWithInfo', '-adobe-*', 5))
{
    %info = %$font;
    print join(" ", %info), "\n";
    print join(" ", @{$info{'min_bounds'}}), "\n";
    print join(" ", @{$info{'max_bounds'}}), "\n";
    %prop = %{$info{'properties'}};
    foreach $atom (keys %prop)
    {
	print $x->atom_name($atom), " => ", $prop{$atom}, "; ";
    }
    print "\n"; 
}
print join(", ", $x->req('GetFontPath')), "\n";
#$x->req('SetFontPath', $x->req('GetFontPath'));
#print join(", ", $x->req('GetFontPath')), "\n";
$pixmap = $x->new_rsrc;
$x->req('CreatePixmap', $pixmap, $win, $x->{'root_depth'}, 50, 50); 
$x->req('FreePixmap', $pixmap);
$gc = $x->new_rsrc;
$x->req('CreateGC', $gc, $win, 'function' => 'Xor', 'line_width' => 2,
	'join_style' => 'Miter', 'font' => $fid, 'arc_mode' => 'PieSlice',
	'foreground' => $x->{'white_pixel'},
	'background' => $x->{'black_pixel'},
	'graphics_exposures' => 0);
$x->req('ChangeGC', $gc, 'join_style' => 'Round');
$fancy_gc = $x->new_rsrc;
$x->req('CreateGC', $fancy_gc, $win);
$x->req('CopyGC', $gc, $fancy_gc, 'function', 'line_width', 'join_style',
	'font', 'arc_mode', 'background', 'graphics_exposures');
$x->req('ChangeGC', $fancy_gc, 'line_style' => 'OnOffDash');
$x->req('SetDashes', $fancy_gc, 0, (1, 2, 1, 3, 1));
$x->req('SetClipRectangles', $fancy_gc, (0, 0), 'UnSorted', [0, 40, 100, 20],
	[40, 0, 20, 100]);
$x->req('ClearArea', $win, (0, 0), 200, 200, 0);
$white = $x->{'white_pixel'};
$black = $x->{'black_pixel'};
$x->req('ChangeGC', $gc, 'function' => 'Copy', 'background' => $white,
	'foreground' => $black);
for (1 .. 500)
{
    push @points, rand(200);
}
$x->PolyPoint($win, $gc, 'Origin', @points);
for $c (@points)
{
    $c = 200 - $c;
}
$x->PolySegment($win, $gc, @points);
for $c (@points)
{
    $c /= 10;
    $c -= 10;
}
$x->ClearArea($win, (0, 0), 200, 200, 0);
$x->PolyLine($win, $gc, 'Previous', (100, 100), @points);
$x->ChangeGC($gc, 'function' => "Xor");
for (1 .. 200)
{
    $x->req('CopyArea', $win, $win, $gc, (rand(160), rand(160)), 40, 40,
	    (rand(160), rand(160)));
}
$x->req('ChangeGC', $gc, 'function' => "Copy");
for (1 .. 200)
{
    $x->req('CopyPlane', $win, $win, $fancy_gc, (rand(160), rand(160)), 
	    40, 40, (rand(160), rand(160)), 1 << 0);
}
$x->req('ClearArea', $win, (0, 0), 200, 200, 0);
for (1 .. 25)
{
    push @rects, [rand(100), rand(100), rand(100), rand(100)];
}
$x->req('PolyRectangle', $win, $gc, @rects);
for (1 .. 16)
{
    push @arcs, [rand(150), rand(150), 50, 50, 0, rand(360 * 64)];
}
$x->req('PolyArc', $win, $gc, @arcs);
$x->req('FillPoly', $win, $gc, 'Convex', 'Origin',
	(100,0)=>(150,150)=>(0,100));
@rects = ();
for (1 .. 100)
{
    push @rects, [rand(190), rand(190), rand(10), rand(10)];
}
$x->req('PolyFillRectangle', $win, $gc, @rects);
@arcs = ();
for (1 .. 25)
{
    push @arcs, [rand(175), rand(175), 25, 25, 90 * 64, rand(360 * 64)];
}
$x->req('PolyFillArc', $win, $gc, @arcs);
$x->req('ClearArea', $win, (0, 0), 200, 200, 0);
if ($x->{'bitmap_bit_order'} eq 'LeastSignificant' 
    and $x->{'bitmap_scanline_unit'} == 32
    and $x->{'bitmap_scanline_pad'} == 32)
{
    $bmap = 
	"\0\0\xff\xff\xff\xff\x0f\0" x 8 .
	"\0\0\xff\0\0\0\xff\0" x 8 .
	"\0\0\xff\xff\xff\xff\x0f\0" x 8 .
	"\0\0\xff\0\0\0\0\0" x 8 .
	"\0\0\xff\0\0\0\0\0" x 8;
    for $shift (0 .. 3)
    {
	$x->req('PutImage', $win, $gc, 1, 56, 40,
		(0, 2 + 42 * $shift), 8, 'Bitmap', $bmap);
    }
}

if (0) 
{
    $pixmap = 
#        1234567890123456789012345678
	"                            ".
	" ####  ##### ####  #        ".
	" #   # #     #   # #        ".
        " ####  ####  ####  #        ".
	" #     #     #   # #        ".
        " #     ##### #   # #####    ".
	"                            ";

    @pixels = unpack("C*", $pixmap);
    for $p (@pixels)
    {
	$p = 0 if $p == ord("#");
    }
    for (1 .. 50)
    {
	@p = @pixels;
	for $p (@p)
	{
	    $p = rand(256) if $p;
	}
	$x->req('PutImage', $win, $gc, 8, 25, 7,
		(rand(175), rand(193)), 0, 'ZPixmap', pack("C*", @p));
    }
}


($d, $v, $image) = $x->req('GetImage', $win, (0, 0), 79, 24, 0xff, 'ZPixmap');
$image =~ tr/\0/ /;
$image =~ tr/ -~/./c;
for $row (0 .. 23)
{
    print substr($image, $row * 80, 80), "\n";
}
$x->req('ClearArea', $win, (0, 0), 200, 200, 0);
$smallfid = $x->new_rsrc;
$x->req('OpenFont', $smallfid, '6x10');
$x->req('PolyText8', $win, $gc, 2, 20, [0, "Hello, "], 
	$smallfid, [-3, "world!"]);
$x->req('PolyText8', $win, $gc, 2, 35, [0, "Perl " x 300]);
#$largefid = $x->new_rsrc;
#$x->req('OpenFont', $largefid, 
#	'-*-*-medium-r-normal--14-*-*-*-c-*-jisx0208.1983-0');
#$x->req('PolyText16', $win, $gc, 2, 50, $largefid, 
#    [0, "\061\101\061\104\061\106\061\110\061\112\061\113\061\114\061\115"
#     . "\061\116\061\117\061\122\061\125\061\130\061\133"]);
$x->req('ChangeGC', $gc, 'font' => $smallfid);
$x->req('ImageText8', $win, $gc, 2, 70, "Perl");
$x->req('ImageText16', $win, $gc, 2, 80, "\0P\0e\0r\0l");
if ($x->{'root_depth'} == 8) {
    $cmap = $x->new_rsrc;
    $x->req('CreateColormap', $cmap, $x->{'root_visual'}, $win, 'All');
    $new_cmap = $x->new_rsrc;
    $x->req('CopyColormapAndFree', $new_cmap, $cmap);
    $x->req('FreeColormap', $cmap);
}
$cmap = $x->{'default_colormap'};
print join(", ", $x->req('ListInstalledColormaps', $win)), "\n";
print join(", ", $x->req('ListInstalledColormaps', $root_wid)), "\n";
($color1, $r, $g, $b) = $x->req('AllocColor', $cmap, 
				1 * 65535, 0 * 65535, 0 * 65535); 
print "$color1 = ($r, $g, $b)\n";
($color2, $r1, $g1, $b1, $r2, $g2, $b2) = 
    $x->req('AllocNamedColor', $cmap, 'orange');
print "orange =~= $color2 =~= ($r1, $g1, $b1) =~= ($r2, $g2, $b2)\n";
if ($x->{'root_depth'} == 8) {
    ($pixels, $masks) = $x->req('AllocColorCells', $cmap, 1, 0, 0);
    $color3 = $pixels->[0];
    print "$color3\n";
    ($rm, $gm, $bm, @pixels) =
	$x->req('AllocColorPlanes', $cmap, 1, (0,0,1), 0);
    print "$rm|$gm|$bm = ", join(", ", @pixels), "\n";
    $x->req('StoreColors', $cmap, [$color3 => (65535, 0, 0)], 
	    [$pixels[0] => (0, 0, 0), 1]);
    $x->req('StoreNamedColor', $cmap, $color3, 'salmon', 7);
}
@colors = $x->req('QueryColors', $cmap, 0 .. 255);
for $c (@colors)
{
    printf "(0x%04x, 0x%04x, 0x%04x), ", @$c;
    print "\n" unless ++$i % 3;
}
print "\n";
($r1, $g1, $b1, $r2, $g2, $b2) = $x->req('LookupColor', $cmap, 'bisque');
print "bisque =~= ($r1, $g1, $b1) =~= ($r2, $g2, $b2)\n";

$fg_pm = $x->new_rsrc;
$x->send('CreatePixmap', $fg_pm, $win, 1, 16, 16); 
$mask_pm = $x->new_rsrc;
$x->send('CreatePixmap', $mask_pm, $win, 1, 16, 16);
$cursor_gc = $x->new_rsrc;
$x->send('CreateGC', $cursor_gc, $fg_pm, 'line_width' => 2,'foreground' => 0); 
$x->send('PolyFillRectangle', $fg_pm, $cursor_gc, [(0, 0), 16, 16]);
$x->send('PolyFillRectangle', $mask_pm, $cursor_gc, [(0, 0), 16, 16]);
$x->send('ChangeGC', $cursor_gc, 'foreground' => 1);
$x->send('PolyArc', $mask_pm, $cursor_gc, [1, 1, 13, 13, 0, 360*64]);
$x->send('ChangeGC', $cursor_gc, 'line_style' => 'OnOffDash');
$x->send('PolyArc', $fg_pm, $cursor_gc, [1, 1, 13, 13, 0, 360*64]);
$cursor = $x->new_rsrc;
$x->send('CreateCursor', $cursor, $fg_pm, $mask_pm, (65535, 0, 0), 
	 (45000, 45000, 45000), (8, 8));
$x->send('ChangeWindowAttributes', $win, 'cursor' => $cursor);
$x->send('FreePixmap', $fg_pm);
$x->send('FreePixmap', $mask_pm);
$x->send('FreeGC', $cursor_gc);

sleep 5;
$cursor_fnt = $x->new_rsrc;
$x->req('OpenFont', $cursor_fnt, 'cursor');
$new_cursor = $x->new_rsrc;
$x->req('CreateGlyphCursor', $new_cursor, $cursor_fnt, $cursor_fnt, 0, 1,
	(65535, 65535, 65535), (0, 0, 0));
$x->req('CloseFont', $cursor_fnt);
$x->req('ChangeWindowAttributes', $win, 'cursor' => $new_cursor);
$x->req('FreeCursor', $cursor);
$cursor = $new_cursor;
for $p (0 .. 10)
{
    $x->req('RecolorCursor', $cursor,
	    (65535, 65535 - $p*6553.5, 65535- $p*6553.5), (0, 0, 0));
    sleep 1;
}
($w, $h) = $x->req('QueryBestSize', 'Cursor', $root_wid, 16, 16);
print "$w x $h is a good size for a cursor.\n";

for $ext ($x->req('ListExtensions'))
{
    ($major, $event, $error) = $x->req('QueryExtension', $ext);
    print "$ext: request $major, event $event, error $error\n";
}

($old) = $x->req('GetKeyboardMapping', $x->{'max_keycode'}, 1);
$x->req('ChangeKeyboardMapping', $x->{'max_keycode'} - 1, 4, 
	[$Keysyms{"a"}, $Keysyms{"A"}, 0, 0],);

$i = $x->min_keycode;
for $ar ($x->req('GetKeyboardMapping', $x->{'min_keycode'}, 
		 $x->{'max_keycode'} - $x->{'min_keycode'} + 1))
#		 10))
{
    print "$i: ", join(", ", map($Keysyms_name{$_} || 'NoSymbol',
					@$ar)), "\n";
    $i++;
}

$x->req('ChangeKeyboardMapping', $x->{'max_keycode'}, scalar(@$old), $old);

%kc = $x->req('GetKeyboardControl');
print join(" ", %kc), "\n";
$bp = $kc{'bell_pitch'};

$x->req('Bell', 100);
$x->req('ChangeKeyboardControl', 'bell_pitch' => 2 * $bp);
sleep 1;
$x->req('Bell', 100);
$x->req('ChangeKeyboardControl', 'bell_pitch' => $bp);

($num, $denom, $thresh) = $x->req('GetPointerControl');
print "Acceleration: $num/$denom; Threshold: $thresh\n";
$x->req('ChangePointerControl', 1, 0, $num * 2, $denom, $thresh);
sleep 2;
$x->req('ChangePointerControl', 1, 0, $num, $denom, $thresh);

($t_out, $interv, $pb, $allow_exp) = $x->req('GetScreenSaver');
print "Timeout: $t_out, Interval: $interv, Blanking: $pb, ";
print "Exposures: $allow_exp\n";
$x->req('SetScreenSaver', $t_out, $interv, $pb, $allow_exp);
($t_out, $interv, $pb, $allow_exp) = $x->req('GetScreenSaver');
print "Timeout: $t_out, Interval: $interv, Blanking: $pb, ";
print "Exposures: $allow_exp\n";

#$addr = pack("C4", (127, 0, 0, 1));
#sen('ChangeHosts', 'Insert', 'Internet', $addr);
($mode, @hosts) = $x->req('ListHosts');
for $ar (@hosts)
{
    print "$ar->[0]: ", join(".", unpack("C4", $ar->[1])), "\n";
}

$x->req('SetAccessControl', $mode);
$x->req('SetCloseDownMode', 'Destroy');
#$x->req('KillClient', 0x200004b);
$x->req('RotateProperties', $win, 1, ($x->req('InternAtom', 'WM_NAME', 1)));
$x->req('ForceScreenSaver', 'Activate');
@map = $x->req('GetPointerMapping');
print join(", ", @map), "\n";
$x->req('SetPointerMapping', @map);
@map = $x->req('GetModifierMapping');
for $ar (@map)
{
    print "[", join(",", @$ar), "]\n";
}
$x->req('SetModifierMapping', @map);

$x->req('NoOperation', 4);

if ($x->{'root_depth'} == 8) {
    $x->req('FreeColors', $cmap, 0, $color1, $color2, $color3, @pixels);
} else {
    $x->FreeColors($cmap, 0, $color1, $color2);
}
$x->req('FreeGC', $fancy_gc);
$x->req('CloseFont', $fid);
$x->req('CloseFont', $smallfid);
#$x->req('CloseFont', $largefid);

$x->init_extensions;

if ($x->{'ext'}{"SHAPE"})
{
    $x->req('ShapeSelectInput', $win, 1);
    $x->req('ShapeRectangles', $win, 'Bounding', 'Set', (0, 0), 'UnSorted',
	    [(0, 0), 50, 50], [(50, 50), 50, 50]);
    $shape_pm = $x->new_rsrc;
    $x->req('CreatePixmap', $shape_pm, $win, 1, 100, 100);
    $shape_gc = $x->new_rsrc;
    $x->req('CreateGC', $shape_gc, $shape_pm, 'foreground' => 0);
    $x->req('PolyFillRectangle', $shape_pm, $shape_gc, [0, 0, 100, 100]);
    $x->req('ChangeGC', $shape_gc, 'foreground' => 1);
    $x->req('PolyFillArc', $shape_pm, $shape_gc, [0, 0, 100, 100, 0, 360*64]);
    $x->req('ShapeMask', $win, 'Bounding', 'Union', 100, 100, $shape_pm);
    $x->req('ShapeCombine', $win, 'Bounding', 'Invert', 0, 0, $x->{'root'},
	    'Bounding');
    $x->req('ShapeOffset', $win, 'Bounding', 25, 25);
    print join(", ", $x->req('ShapeQueryExtents', $win)), "\n";
    print $x->req('ShapeInputSelected', $win), "\n";
    ($ordering, @rects) = $x->req('ShapeGetRectangles', $win, 'Bounding');
    print "Ordering: $ordering\n";
    for $rr (@rects)
    {
	print "[", join(", ", @$rr), "], ";
    }
    print "\n";
}

# This should be last, since it's a REAL memory hog.
if ($x->{'ext'}{'BIG_REQUESTS'})
{
    print "Maximum request length: ", $x->maximum_request_length * 4, "\n";
    for $i (1 .. 65536)
    {
        push @points, int(rand(200)), int(rand(200));
    }
    $x->PolyPoint($win, $gc, 'Origin', @points);
}

#print_event(%e) while %e = $x->dequeue_event;
#$x->{'event_handler'} = \&print_event; 

$x->req('FreeGC', $gc);

$done = 1;
$x->handle_input while 1;
#print_event(%e) while %e = $x->next_event
