The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#!/usr/bin/perl

# Put up windows on two displays, and echo input from one on the other.

use X11::Protocol;
use X11::Keysyms '%keysyms';
%keysyms_name = reverse %keysyms;

use IO::Select;

die "usage: $0 display display\n" unless @ARGV == 2;

$x1 = X11::Protocol->new($ARGV[0]);
$x2 = X11::Protocol->new($ARGV[1]);

$win1 = $x1->new_rsrc;
$win2 = $x2->new_rsrc;

$x1->CreateWindow($win1, $x1->root, 'InputOutput', $x1->root_depth,
		  'CopyFromParent', (0,0), 200, 200, 1,
#		  'backing_store' => 'Always',
		  'background_pixel' => $x1->white_pixel,
		  'event_mask' => $x1->pack_event_mask('KeyPress', 'Exposure',
						       'ButtonPress'));
$x2->CreateWindow($win2, $x2->root, 'InputOutput', $x2->root_depth,
		  'CopyFromParent', (0,0), 200, 200, 1,
#		  'backing_store' => 'Always',
		  'background_pixel' => $x2->white_pixel,
		  'event_mask' => $x2->pack_event_mask('KeyPress', 'Exposure',
						       'ButtonPress'));

$x1->ChangeProperty($win1, $x1->atom("WM_NAME"), $x1->atom("STRING"), 8,
		    'Replace', "Window #1");
$x2->ChangeProperty($win2, $x2->atom("WM_NAME"), $x2->atom("STRING"), 8,
		    'Replace', "Window #2");

$x1->MapWindow($win1);
$x2->MapWindow($win2);

$fnt1 = $x1->new_rsrc;
$fnt2 = $x2->new_rsrc;

$x1->OpenFont($fnt1, 'fixed');
$x2->OpenFont($fnt2, 'fixed');

$gc1 = $x1->new_rsrc;
$gc2 = $x2->new_rsrc;

$x1->CreateGC($gc1, $win1, 'foreground' => $x1->black_pixel, 'font' => $fnt1,
	      'graphics_exposures' => 0);
$x2->CreateGC($gc2, $win2, 'foreground' => $x2->black_pixel, 'font' => $fnt2,
	      'graphics_exposures' => 0);

$i = $x1->min_keycode;
for $ar ($x1->GetKeyboardMapping($x1->min_keycode,
				 $x1->max_keycode - $x1->min_keycode + 1)) {
    $table[0][$i++] = [map($keysyms_name{$_}, @$ar)];
}

$i = $x2->min_keycode;
for $ar ($x2->GetKeyboardMapping($x2->min_keycode,
				 $x2->max_keycode - $x2->min_keycode + 1)) {
    $table[1][$i++] = [map($keysyms_name{$_}, @$ar)];
}

sub print_event {
    my($disp, $win, $gc, $font, $t, %e) = @_;
    if ($e{name} eq "KeyPress") {
	my($key) = $t->[$e{detail}][0];
	exit if $key eq "q" or $key eq "Q";
	$disp->PolyText8($win, $gc, ($e{event_x}, $e{event_y}), [0, $key]);
	(my $key16 = $key) =~ s/(.)/\0$1/g;;	
	my $dx = {$disp->QueryTextExtents($font, $key16)}->{'overall_width'};
	$disp->WarpPointer(0, 0, 0, 0, 0, 0, $dx, 0);
    } elsif ($e{name} eq "ButtonPress") {
	$disp->PolyPoint($win, $gc, 'Origin', ($e{event_x}, $e{event_y}));
    } elsif ($e{name} eq "Expose") {
	$disp->PolyRectangle($win, $gc, [($e{'x'}, $e{'y'}), $e{width},
			     $e{height}]);
    }
}

$x1->event_handler(sub {print_event($x2, $win2, $gc2, $fnt2, $table[1], @_)});
$x2->event_handler(sub {print_event($x1, $win1, $gc1, $fnt1, $table[0], @_)});

$sel = IO::Select->new($x1->connection->fh, $x2->connection->fh);

for (;;) {
    for $fh ($sel->can_read) {
	$x1->handle_input if $fh == $x1->connection->fh;
	$x2->handle_input if $fh == $x2->connection->fh;
    }
}