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