1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
#!/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 <http://www.gnu.org/licenses/>.
# 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;
|