File Coverage

blib/lib/X11/WindowHierarchy.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package X11::WindowHierarchy;
2             # ABSTRACT: Retrieve information from X11 windows
3 2     2   24274 use strict;
  2         6  
  2         89  
4 2     2   12 use warnings;
  2         5  
  2         70  
5 2     2   1730 use parent qw(Exporter);
  2         778  
  2         12  
6              
7             our $VERSION = '0.004';
8              
9             =head1 NAME
10              
11             X11::WindowHierarchy - wrapper around L for retrieving the current window hierarchy
12              
13             =head1 VERSION
14              
15             version 0.004
16              
17             =head1 SYNOPSIS
18              
19             use X11::WindowHierarchy;
20              
21             # Returns a list of all windows with at least one 'word' character in the
22             # window title, using the current $ENV{DISPLAY} to select the display and
23             # screen
24             my @windows = x11_filter_hierarchy(
25             filter => qr/\w/
26             );
27             printf "Found window [%s] (id %d)%s\n", $_->{title}, $_->{id}, $_->{pid} ? ' pid ' . $_->{pid} : '' for @windows;
28              
29             # Dump all information we have about all windows on display :1
30             use Data::TreeDumper;
31             print DumpTree(x11_hierarchy(display => ':1'));
32              
33             =head1 DESCRIPTION
34              
35             Provides a couple of helper functions based on L for
36             extracting the current window hierarchy.
37              
38             =cut
39              
40 2     2   2288 use X11::Protocol;
  0            
  0            
41              
42             our @EXPORT_OK = qw(x11_hierarchy x11_filter_hierarchy);
43             our @EXPORT = qw(x11_hierarchy x11_filter_hierarchy);
44              
45             =head1 FUNCTIONS
46              
47             The following functions are exported by default, to avoid this:
48              
49             use X11::WindowHierarchy qw();
50              
51             =cut
52              
53             =head2 x11_hierarchy
54              
55             Returns a hashref representing the current window hierarchy.
56              
57             Takes the following named parameters, all of which are optional:
58              
59             =over 4
60              
61             =item * display - DISPLAY string, such as ':0'
62              
63             =item * screen - the screen to use, such as 0 or 1
64              
65             =back
66              
67             Returns a hashref structure which contains the following keys:
68              
69             =over 4
70              
71             =item * id - the ID for this window
72              
73             =item * parent - the ID for the parent window
74              
75             =item * pid - the process ID for this window, if it has one
76              
77             =item * title - the window name, with any vertical whitespace (such as \n) converted to a single space
78              
79             =item * icon_name - the icon name
80              
81             =item * children - an arrayref of any child windows under this
82              
83             =back
84              
85             =cut
86              
87             sub x11_hierarchy {
88             my %args = @_;
89              
90             # Only pass display if we have it
91             my $x = X11::Protocol->new(exists $args{display} ? (delete $args{display}) : ());
92             my $screen = delete $args{screen} || 0;
93              
94             # Tree walker
95             my $code; $code = sub {
96             # We get a window ID.
97             my $win = shift;
98              
99             # Extract all the properties we can
100             my %props = map {
101             # not entirely sure of the correct parameters for the API here, but, uh... "seems to work"
102             $_->[0] => ($x->GetProperty($win, $_->[1], 'AnyPropertyType', 0, 255))[0]
103             } map [
104             # pretty sure this only returns a scalar, and if it doesn't then we'll break in other ways,
105             # but the tests will save us!
106             $_ => scalar $x->atom($_, 1)
107             ], qw(
108             _NET_WM_ICON_NAME
109             _NET_WM_NAME
110             _NET_WM_PID
111             );
112              
113             # Get all the geometry info apart from the root, since we know that already
114             my %geom = $x->GetGeometry($win);
115             delete $geom{root};
116             @props{keys %geom} = values %geom;
117              
118             # Apply our ID
119             $props{id} = $win;
120              
121             # Grab the pid if we have it
122             if(my $pid = delete $props{_NET_WM_PID}) {
123             $props{pid} = unpack 'L1', $pid;
124             }
125              
126             # Get rid of any \n or similar chars, which seem to be legal in window titles for example
127             s/[\r\f\n\t\x0B]+/ /g for grep defined, values %props;
128              
129             # Remap to something more friendly
130             $props{title} = delete $props{_NET_WM_NAME};
131             $props{icon_name} = delete $props{_NET_WM_ICON_NAME};
132              
133             # Pull a list of all the child windows
134             my (undef, $parent, @kids) = $x->QueryTree($win);
135              
136             # TODO seems to be consistent, but should check on l10n
137             undef $parent if $parent eq 'None';
138             $props{parent} = $parent if $parent;
139              
140             # ... and recurse for each child window.
141             $props{children} = [ ];
142             push @{$props{children}}, $code->($_, $win) for @kids;
143             return \%props;
144             };
145              
146             # Start at the root, work down.
147             my $tree = $code->($x->{screens}[$screen]{root});
148              
149             # and we're done.
150             return $tree;
151             }
152              
153             =head2 x11_filter_hierarchy
154              
155             Similar to L function, but instead of returning a tree hierarchy,
156             returns a list of windows which match the given criteria.
157              
158             Takes the same parameters as L, with the addition of a C< filter >
159             parameter.
160              
161             If given a coderef as the filter, this will be called for each window found,
162             including the window in the output list if the coderef returns a true value.
163             The hashref representing the window will be passed as the first parameter and
164             for convenience is also available in $_. The full hierarchy will be constructed
165             before filtering the list of windows, so you can perform matches based on
166             the child elements if required.
167              
168             If given a regex as the filter, returns only the windows whose title matches
169             the given regex.
170              
171             =cut
172              
173             sub x11_filter_hierarchy {
174             my %args = @_;
175             my $code = delete $args{filter};
176              
177             if(ref($code) eq 'Regexp') {
178             my $re = $code;
179             $code = sub { return unless defined $_->{title}; $_->{title} =~ /$re/ };
180             }
181              
182             my @out;
183             my @pending = x11_hierarchy(%args);
184             while(@pending) {
185             my $item = shift @pending;
186             push @pending, @{$item->{children}};
187              
188             # Pass in $_[0] and $_ for convenience.
189             push @out, $item for grep $code->($item), $item;
190             }
191             @out
192             }
193              
194             1;
195              
196             __END__