#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of X11-Protocol-Other. # # X11-Protocol-Other 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 3, or (at your option) any # later version. # # X11-Protocol-Other 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 X11-Protocol-Other. If not, see . # Usage: perl damage-duplicate.pl # perl damage-duplicate.pl --id 0x120000f # # This is an example of duplicating the contents of a window in real-time by # listening for changes to it with the DAMAGE extension. # # A new $window toplevel displays the contents of a $source window. The key # feature of the damage extension is that it reports when $source changes. # Without that a duplicating program like this would have to re-copy every 1 # second or something like that. # # The source window can be given as an XID with the "--id" command line # option, otherwise an X11::Protocol::ChooseWindow is run so you can click # on a window like xwininfo does. # # Details: # # In the event_handler() code care is taken not to do anything which reads a # reply. This is because reading the reply may also read and process other # events, which would call event_handler() recursively and possibly # endlessly. Any event handler should bear that in mind. In this program # the danger would be that if the $source window is changing rapidly and so # causing a new DamageNotify event to come very soon after each # DamageSubtract(). # # The $gc used for copying has "graphics_expose" off, which means any parts # not available in the source window are cleared to the destination # background colour. This happens when the source is overlapped etc by # other windows. # # In the core protocol there's no easy way to get content from $source when # it's overlapped, since the server generally doesn't keep the contents or # generate expose events for obscured parts of windows. # # If the "Composite" extension is available then it retains content of # overlapped windows. CompositeRedirectWindow() in the setups is all that's # needed to have full $source contents available for the CopyArea()s. # # For simplicity the entire source area is copied whenever it changes. In a # more sophisticated program the "$parts_region" of changes from the damage # object could be a clip mask for the CopyArea(). Changes outside the # duplicated area would then still go back and forward as DamageNotify and # CopyArea, but the server would see from the clip region no actual drawing # required. # # If $window is bigger than the $source then the excess is cleared. Some # care is taken to clear only the excess area, not the whole of $window, # since the latter way would make it flash to black and then to the copied # $source. On a fast screen you might not notice, but on a slow screen or # if the server is bogged down then such flashing is very unattractive. # # Shortcomings: # # The created $window is always on the same screen as $source and uses the # same depth, visual and colormap. Doing so means a simple CopyArea # suffices to copy the contents across. # # If source and destination were different depth, visual or colormap then # pixel colour conversions would be required. If the destination was on a # different server or different screen then some data transfers with # GetImage() and PutImage() would be needed as well as pixel conversions. # (As usual X11::Protocol::Ext::MIT_SHM might do that through shared memory # if the program is on the same machine as the server.) # # Duplicating the root window is specifically disallowed. The problem is # that a draw to $window is a change to the root window contents, so # generates another DamageNotify, which does another draw, etc, in an # infinite loop. It might work if attention was paid to what parts of the # root had changed. Changes to the part of the root which is unobscured # parts of $window will be due to the duplicating drawing and so don't # require any further drawing. # BEGIN { require 5 } use strict; use Getopt::Long; use X11::AtomConstants; use X11::CursorFont; use X11::Protocol; use X11::Protocol::WM; # uncomment this to run the ### lines #use Smart::Comments; my $X = X11::Protocol->new (':0'); if (! $X->init_extension('DAMAGE')) { print "DAMAGE extension not available on the server\n"; exit 1; } #------------------------------------------------------------------------------ # command line my $source; # source window to duplicate my $verbose = 1; GetOptions ('id=s' => \$source, 'verbose+' => \$verbose) or exit 1; #------------------------------------------------------------------------------ # source window, from command line or chosen my $popup_time; if (defined $source) { # command line --id=XID $source = oct($source); # oct() for hex 0xA0000F style as well as decimal } else { require X11::Protocol::ChooseWindow; print "Click on a window to duplicate ...\n"; $source = X11::Protocol::ChooseWindow->choose (X => $X); print " got it\n"; } if ($verbose) { printf "Source window %d (0x%X)\n", $source, $source; } if ($source == $X->root) { print "Cannot duplicate root window\n"; exit 1; } #------------------------------------------------------------------------------ # use the Composite extension, if available, to keep the contents of $source # if it's overlapped by other windows. if ($X->init_extension('Composite')) { $X->CompositeRedirectWindow ($source, 'Automatic'); } #------------------------------------------------------------------------------ my %source_geom = $X->GetGeometry($source); my %source_attr = $X->GetWindowAttributes($source); # create new output window to show a duplicate of $source # same depth, visual, colormap my $window = $X->new_rsrc; $X->CreateWindow ($window, $X->root, # parent 'InputOutput', # class $source_geom{'depth'}, $source_attr{'visual'}, 0,0, # x,y 100,100, # w,h initial size 0, # border colormap => $source_attr{'colormap'}, background_pixel => $X->black_pixel, event_mask => $X->pack_event_mask('Exposure'), ); X11::Protocol::WM::set_wm_class ($X, $window, 'damage-duplicate', 'DamageDuplicate'); X11::Protocol::WM::set_wm_name ($X, $window, 'Duplicate Window'); # title X11::Protocol::WM::set_wm_icon_name ($X, $window, 'Duplicate'); X11::Protocol::WM::set_wm_client_machine_from_syshostname ($X, $window); X11::Protocol::WM::set_net_wm_pid ($X, $window); X11::Protocol::WM::set_net_wm_user_time($X, $window, $popup_time); $X->MapWindow ($window); # select ConfigureNotify from $source, to know when it resizes $X->ChangeWindowAttributes ($source, event_mask => $X->pack_event_mask('StructureNotify')); # the damage object to monitor $source # creating this gives DamageNotify events my $damage = $X->new_rsrc; $X->DamageCreate ($damage, $source, 'NonEmpty'); my $gc = $X->new_rsrc; $X->CreateGC ($gc, $window, subwindow_mode => 'IncludeInferiors', # no "graphics exposures", don't want GraphicsExpose events if # a part of the $X->CopyArea is obscured graphics_exposures => 0); sub event_handler { my (%h) = @_; my $name = $h{'name'}; ### event_handler() ### $name if ($name eq 'ConfigureNotify') { # $source has resized ### height: $h{'height'} my $width = $h{'width'}; # of $source my $height = $h{'height'}; # clear any excess if $source has shrunk $X->ClearArea ($window, $width,0, 0,0); # to left of $width $X->ClearArea ($window, 0,$height, 0,0); # below $height # copy any extra if $source has expanded $X->CopyArea ($source, $window, $gc, 0,0, # src x,y $h{'width'},$h{'height'}, # src w,h 0,0); # dst x,y } elsif ($name eq 'DamageNotify') { # $source has been drawn into my $rect = $h{'geometry'}; my ($root_x, $root_y, $width, $height) = @$rect; ### $rect $X->DamageSubtract ($damage, 'None', 'None'); $X->CopyArea ($source, $window, $gc, 0,0, # src x,y $width,$height, 0,0); # dst x,y } elsif ($name eq 'Expose') { # our $window revealed, draw it $X->CopyArea ($source, $window, $gc, $h{'x'},$h{'y'}, # src x,y $h{'width'},$h{'height'}, # src w,h $h{'x'},$h{'y'}); # dst x,y } } $X->{'event_handler'} = \&event_handler; for (;;) { $X->handle_input; } exit 0;