1# Copyright (c) 2004-2009 Mikhael Goikhman, Scott Smedley
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, see: <http://www.gnu.org/licenses/>
15
16package FVWM::Tracker::WindowList;
17
18use strict;
19
20use FVWM::Tracker qw(base);
21
22my $window_events = M_ADD_WINDOW | M_CONFIGURE_WINDOW | M_DESTROY_WINDOW |
23	M_ICONIFY | M_DEICONIFY;
24my $name_events = M_RES_NAME | M_RES_CLASS | M_WINDOW_NAME | M_VISIBLE_NAME |
25	M_ICON_NAME;
26my $name_xevents = MX_VISIBLE_ICON_NAME;
27my $stack_events = M_RESTACK | M_RAISE_WINDOW | M_LOWER_WINDOW;
28my $icon_events = M_ICON_LOCATION | M_ICON_FILE | M_DEFAULTICON | M_MINI_ICON;
29
30sub observables ($) {
31	return [
32		"window added",
33		"window deleted",
34		"window properties updated",
35		"window moved",
36		"window resized",
37		"window iconified",
38		"window deiconified",
39		"window name updated",
40		"window stack updated",
41		"window icon updated",
42	];
43}
44
45sub new ($$;$) {
46	my $class = shift;
47	my $module = shift;
48	my @options = split(/ /, shift || "");
49
50	my $self = $class->FVWM::Tracker::new($module);
51
52	$self->{options} = [ @options ];
53	return $self;
54}
55
56sub add_requested_info_handlers ($$) {
57	my $self = shift;
58	my $handler = shift;
59
60	my $use_winfo = 1;
61	my $use_names = 1;
62	my $use_stack = 0;
63	my $use_icons = 0;
64	foreach (@{$self->{options}}) {
65		/^(\!?)winfo$/ and $use_winfo = $1 ne '!';
66		/^(\!?)names$/ and $use_names = $1 ne '!';
67		/^(\!?)stack$/ and $use_stack = $1 ne '!';
68		/^(\!?)icons$/ and $use_icons = $1 ne '!';
69	}
70	my $mask  = 0;
71	$mask |= $window_events if $use_winfo;
72	$mask |= $name_events   if $use_names;
73	$mask |= $stack_events  if $use_stack;
74	$mask |= $icon_events   if $use_icons;
75
76	my $xmask = 0;
77	$xmask |= $name_xevents if $use_names;
78
79	$self->add_handler($mask, $handler) if $mask;
80	$self->add_handler($xmask, $handler) if $xmask;
81	$self->add_handler(M_NEW_PAGE | M_NEW_DESK, sub {
82		$self->handler_page_info($_[1]);
83	}) if $use_winfo;
84}
85
86sub start ($) {
87	my $self = shift;
88
89	$self->{data} = {};
90
91	$self->add_requested_info_handlers(sub {
92		my $event = $_[1];
93		$self->calculate_internals($event);
94	});
95
96	$self->request_windowlist_events;
97	my $result = $self->SUPER::start;
98	$self->delete_handlers;
99
100	$self->add_requested_info_handlers(sub {
101		my $event = $_[1];
102		my ($win_id, $old_hash) = $self->calculate_internals($event);
103		return unless defined $win_id;
104		my $type = $event->type();
105		my $observable = undef;
106
107		if ($self->{module}->is_event_extended($type)) {
108			if ($type & $name_xevents) {
109				$observable = "window name updated";
110			}
111		} elsif ($type & M_ADD_WINDOW) {
112			$observable = "window added";
113		} elsif ($type & M_CONFIGURE_WINDOW) {
114			$observable = "window properties updated";
115
116			# this observable is too broad, try to narrow
117			if ($old_hash) {
118				my $win = $self->{data}->{$win_id};
119
120				$self->notify("window resized", $win_id, $old_hash)
121					if $win->{width} != $old_hash->{width}
122					|| $win->{height} != $old_hash->{height};
123
124				$self->notify("window moved", $win_id, $old_hash)
125					if $win->{desk} != $old_hash->{desk}
126					|| $win->{x} != $old_hash->{x}
127					|| $win->{y} != $old_hash->{y};
128			}
129		} elsif ($type & M_DESTROY_WINDOW) {
130			$observable = "window deleted";
131		} elsif ($type & M_ICONIFY) {
132			$observable = "window iconified";
133		} elsif ($type & M_DEICONIFY) {
134			$observable = "window deiconified";
135		} elsif ($type & $name_events) {
136			$observable = "window name updated";
137		} elsif ($type & $stack_events) {
138			$observable = "window stack updated";
139		} elsif ($type & $icon_events) {
140			$observable = "window icon updated";
141		}
142		$self->notify($observable, $win_id, $old_hash) if $observable;
143	});
144
145	return $result;
146}
147
148sub calculate_internals ($$) {
149	my $self = shift;
150	my $event = shift;
151	my $args = $event->args;
152	my $data = $self->{data};
153	my $win_id = $args->{win_id};
154
155	my $old_hash = undef;
156	$old_hash = { %{$data->{$win_id}} } if defined $data->{$win_id};
157
158	my $window = $data->{$win_id} ||=
159		bless { id => $win_id, iconified => 0, _tracker => $self },
160		"FVWM::Window";
161
162	# There are some fields that are not unique to all events. To ensure
163	# we don't clobber them, we rename some fields. For example, the 'name'
164	# field of M_MINI_ICON events is renamed to 'mini_icon_name'.
165	foreach ('name', 'x', 'y', 'width', 'height') {
166		if (defined $args->{$_}) {
167			(my $name = lc($event->name())) =~ s/^.*?_//;
168			$name .= "_$_" if ($name !~ /$_$/);
169			$args->{$name} = $args->{$_};
170			delete $args->{$_};
171		}
172	}
173
174	#print $event->dump;
175
176	$args->{name} = delete $args->{window_name} if exists $args->{window_name};
177	@$window{keys %$args} = values %$args;
178
179	if (defined $args->{frame_x}) {
180		# frame_x & frame_y are _relative_ coords of the window to the
181		# current page - calculate the _absolute_ coords - x & y.
182		$window->{x} = delete $window->{frame_x};
183		$window->{y} = delete $window->{frame_y};
184		$window->{width}  = delete $window->{frame_width};
185		$window->{height} = delete $window->{frame_height};
186		my $page = $self->{page_info};
187		if (defined $page) {
188			$window->{X} = $page->{vp_x} + $window->{x};
189			$window->{Y} = $page->{vp_y} + $window->{y};
190			$window->{page_nx} = int($window->{X} / $page->{vp_width});
191			$window->{page_ny} = int($window->{Y} / $page->{vp_height});
192		}
193	}
194
195	my $type = $event->type();
196	if (!$self->{module}->is_event_extended($type)) {
197		if ($type & M_DEICONIFY) {
198			$window->{iconified} = 0;
199		} elsif ($type & M_ICONIFY) {
200			$window->{iconified} = 1;
201		} elsif ($type & M_DESTROY_WINDOW) {
202			delete $data->{$win_id};
203		}
204	}
205
206	return wantarray ? ($win_id, $old_hash) : $win_id;
207}
208
209sub handler_page_info ($$) {
210	my $self = shift;
211	my $event = shift;
212	my $args = $event->args;
213	my $data = $self->{page_info} ||= {};
214
215	@$data{keys %$args} = values %$args;
216	if ($event->type & M_NEW_PAGE) {
217		$data->{page_nx} = int($data->{vp_x} / $data->{vp_width});
218		$data->{page_ny} = int($data->{vp_y} / $data->{vp_height});
219	}
220}
221
222sub page_info ($;$) {
223	my $self = shift;
224	my $id = shift;
225	my $data = $self->{page_info};
226	return $data unless defined $id;
227	return $data->{$id};
228}
229
230sub data ($;$) {
231	my $self = shift;
232	my $id = shift;
233	my $data = $self->{data};
234	return $data unless defined $id;
235	return $data->{$id};
236}
237
238sub dump ($;$) {
239	my $self = shift;
240	my $id = shift;
241	my $data = $self->{data};
242	my @ids = defined $id? ($id): sort { $a <=> $b } keys %$data;
243
244	my $string = "";
245	foreach (@ids) {
246		my $window = $data->{$_};
247		$string .= $window->dump;
248	}
249	return $string;
250}
251
252sub windows ($) {
253	my $self = shift;
254	my @windows = values %{$self->data};
255	return wantarray? @windows: \@windows;
256}
257
258# ----------------------------------------------------------------------------
259
260package FVWM::Window;
261
262sub match ($$) {
263	my $self = shift;
264	my $condition = shift;
265	my @conditions = split(/[,\s]+/, $condition);
266
267	my $match = 1;
268	foreach (@conditions) {
269		my $opposite = s/^!//;
270		if (/^iconified$/i) {
271			return 0 unless $opposite ^ $self->{iconified};
272		} elsif (/^current(page|desk)$/i) {
273			my $page = $self->{_tracker}->page_info;
274			return 0 unless $opposite ^ ($self->{desk} == $page->{desk_n});
275			next if lc($1) eq "desk";
276			return 0 unless $opposite ^ (
277				$self->{x} + $self->{width} > $page->{vp_x} &&
278				$self->{x} < $page->{vp_x} + $page->{vp_width} &&
279				$self->{y} + $self->{height} > $page->{vp_y} &&
280				$self->{y} < $page->{vp_y} + $page->{vp_height}
281			);
282		}
283	}
284	return $match;
285}
286
287sub dump ($) {
288	my $self = shift;
289	my $id = $self->{win_id};
290	my $string = "Window $id\n";
291	foreach my $prop (sort keys %$self) {
292		next if $prop =~ /^_/;
293		$string .= "\t$prop:\t[$self->{$prop}]\n";
294	}
295	return $string;
296}
297
298# ----------------------------------------------------------------------------
299
3001;
301
302__END__
303
304=head1 DESCRIPTION
305
306This is a subclass of B<FVWM::Tracker> that enables to read the window
307information.
308
309This tracker defines the following observables:
310
311    "window added",
312    "window deleted",
313    "window properties updated",
314    "window moved",
315    "window resized",
316    "window iconified",
317    "window deiconified",
318    "window name updated",
319    "window stack updated",
320    "window icon updated",
321
322=head1 SYNOPSYS
323
324Using B<FVWM::Module> $module object:
325
326    my $tracker = $module->track("WindowList");
327    my @windows = $tracker->windows;
328    foreach my $window ($tracker->windows) {
329        print "+$window->{x}+$window->{y}, $window->{name}\n";
330    }
331
332or:
333
334    my $tracker = $module->track("WindowList", "winfo");
335    my $x = $tracker->data("0x230002a")->{x};
336
337or:
338
339    my $tracker = $module->track("WindowList", $options);
340    my $data = $tracker->data;
341    while (my ($win_id, $window) = each %$data) {
342        next unless $window->match("CurrentPage, Iconified");
343        $module->send("Iconify off", $win_id);
344    }
345
346Default $options string is: "!stack !icons names winfo"
347
348=head1 OVERRIDDEN METHODS
349
350=over 4
351
352=item B<new> I<module> I<params>
353
354It is possible the kind of window list.
355
356To be written.
357
358=item B<data> [I<window-id>]
359
360Returns hash ref of window hash refs. or one window hash ref if
361I<window-id> is given.
362
363=item B<dump> [I<window-id>]
364
365Works similarly to B<data>, but returns debug lines for one or all windows.
366
367=back
368
369=head1 METHODS
370
371=over 4
372
373=item B<page_info> [I<field>]
374
375Returns hash ref of page/desk info, or actual hash value using B<field> as a key (if specified).
376
377=back
378
379=head1 AUTHORS
380
381=over 4
382
383=item Mikhael Goikhman <migo@homemail.com>
384
385=item Scott Smedley
386
387=back
388
389=head1 SEE ALSO
390
391For more information, see L<FVWM::Module> and L<FVWM::Tracker>.
392
393=cut
394